1 // 4.1.6 (2014-10-08)
  2 
  3 /**
  4  * Compiled inline version. (Library mode)
  5  */
  6 
  7 /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
  8 /*globals $code */
  9 
 10 (function(exports, undefined) {
 11 	"use strict";
 12 
 13 	var modules = {};
 14 
 15 	function require(ids, callback) {
 16 		var module, defs = [];
 17 
 18 		for (var i = 0; i < ids.length; ++i) {
 19 			module = modules[ids[i]] || resolve(ids[i]);
 20 			if (!module) {
 21 				throw 'module definition dependecy not found: ' + ids[i];
 22 			}
 23 
 24 			defs.push(module);
 25 		}
 26 
 27 		callback.apply(null, defs);
 28 	}
 29 
 30 	function define(id, dependencies, definition) {
 31 		if (typeof id !== 'string') {
 32 			throw 'invalid module definition, module id must be defined and be a string';
 33 		}
 34 
 35 		if (dependencies === undefined) {
 36 			throw 'invalid module definition, dependencies must be specified';
 37 		}
 38 
 39 		if (definition === undefined) {
 40 			throw 'invalid module definition, definition function must be specified';
 41 		}
 42 
 43 		require(dependencies, function() {
 44 			modules[id] = definition.apply(null, arguments);
 45 		});
 46 	}
 47 
 48 	function defined(id) {
 49 		return !!modules[id];
 50 	}
 51 
 52 	function resolve(id) {
 53 		var target = exports;
 54 		var fragments = id.split(/[.\/]/);
 55 
 56 		for (var fi = 0; fi < fragments.length; ++fi) {
 57 			if (!target[fragments[fi]]) {
 58 				return;
 59 			}
 60 
 61 			target = target[fragments[fi]];
 62 		}
 63 
 64 		return target;
 65 	}
 66 
 67 	function expose(ids) {
 68 		for (var i = 0; i < ids.length; i++) {
 69 			var target = exports;
 70 			var id = ids[i];
 71 			var fragments = id.split(/[.\/]/);
 72 
 73 			for (var fi = 0; fi < fragments.length - 1; ++fi) {
 74 				if (target[fragments[fi]] === undefined) {
 75 					target[fragments[fi]] = {};
 76 				}
 77 
 78 				target = target[fragments[fi]];
 79 			}
 80 
 81 			target[fragments[fragments.length - 1]] = modules[id];
 82 		}
 83 	}
 84 
 85 // Included from: js/tinymce/classes/dom/EventUtils.js
 86 
 87 /**
 88  * EventUtils.js
 89  *
 90  * Copyright, Moxiecode Systems AB
 91  * Released under LGPL License.
 92  *
 93  * License: http://www.tinymce.com/license
 94  * Contributing: http://www.tinymce.com/contributing
 95  */
 96 
 97 /*jshint loopfunc:true*/
 98 /*eslint no-loop-func:0 */
 99 
100 /**
101  * This class wraps the browsers native event logic with more convenient methods.
102  *
103  * @class tinymce.dom.EventUtils
104  */
105 define("tinymce/dom/EventUtils", [], function() {
106 	"use strict";
107 
108 	var eventExpandoPrefix = "mce-data-";
109 	var mouseEventRe = /^(?:mouse|contextmenu)|click/;
110 	var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1};
111 
112 	/**
113 	 * Binds a native event to a callback on the speified target.
114 	 */
115 	function addEvent(target, name, callback, capture) {
116 		if (target.addEventListener) {
117 			target.addEventListener(name, callback, capture || false);
118 		} else if (target.attachEvent) {
119 			target.attachEvent('on' + name, callback);
120 		}
121 	}
122 
123 	/**
124 	 * Unbinds a native event callback on the specified target.
125 	 */
126 	function removeEvent(target, name, callback, capture) {
127 		if (target.removeEventListener) {
128 			target.removeEventListener(name, callback, capture || false);
129 		} else if (target.detachEvent) {
130 			target.detachEvent('on' + name, callback);
131 		}
132 	}
133 
134 	/**
135 	 * Normalizes a native event object or just adds the event specific methods on a custom event.
136 	 */
137 	function fix(originalEvent, data) {
138 		var name, event = data || {}, undef;
139 
140 		// Dummy function that gets replaced on the delegation state functions
141 		function returnFalse() {
142 			return false;
143 		}
144 
145 		// Dummy function that gets replaced on the delegation state functions
146 		function returnTrue() {
147 			return true;
148 		}
149 
150 		// Copy all properties from the original event
151 		for (name in originalEvent) {
152 			// layerX/layerY is deprecated in Chrome and produces a warning
153 			if (!deprecated[name]) {
154 				event[name] = originalEvent[name];
155 			}
156 		}
157 
158 		// Normalize target IE uses srcElement
159 		if (!event.target) {
160 			event.target = event.srcElement || document;
161 		}
162 
163 		// Calculate pageX/Y if missing and clientX/Y available
164 		if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
165 			var eventDoc = event.target.ownerDocument || document;
166 			var doc = eventDoc.documentElement;
167 			var body = eventDoc.body;
168 
169 			event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
170 				(doc && doc.clientLeft || body && body.clientLeft || 0);
171 
172 			event.pageY = originalEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) -
173 				(doc && doc.clientTop  || body && body.clientTop  || 0);
174 		}
175 
176 		// Add preventDefault method
177 		event.preventDefault = function() {
178 			event.isDefaultPrevented = returnTrue;
179 
180 			// Execute preventDefault on the original event object
181 			if (originalEvent) {
182 				if (originalEvent.preventDefault) {
183 					originalEvent.preventDefault();
184 				} else {
185 					originalEvent.returnValue = false; // IE
186 				}
187 			}
188 		};
189 
190 		// Add stopPropagation
191 		event.stopPropagation = function() {
192 			event.isPropagationStopped = returnTrue;
193 
194 			// Execute stopPropagation on the original event object
195 			if (originalEvent) {
196 				if (originalEvent.stopPropagation) {
197 					originalEvent.stopPropagation();
198 				} else {
199 					originalEvent.cancelBubble = true; // IE
200 				}
201 			}
202 		};
203 
204 		// Add stopImmediatePropagation
205 		event.stopImmediatePropagation = function() {
206 			event.isImmediatePropagationStopped = returnTrue;
207 			event.stopPropagation();
208 		};
209 
210 		// Add event delegation states
211 		if (!event.isDefaultPrevented) {
212 			event.isDefaultPrevented = returnFalse;
213 			event.isPropagationStopped = returnFalse;
214 			event.isImmediatePropagationStopped = returnFalse;
215 		}
216 
217 		return event;
218 	}
219 
220 	/**
221 	 * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
222 	 * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
223 	 */
224 	function bindOnReady(win, callback, eventUtils) {
225 		var doc = win.document, event = {type: 'ready'};
226 
227 		if (eventUtils.domLoaded) {
228 			callback(event);
229 			return;
230 		}
231 
232 		// Gets called when the DOM is ready
233 		function readyHandler() {
234 			if (!eventUtils.domLoaded) {
235 				eventUtils.domLoaded = true;
236 				callback(event);
237 			}
238 		}
239 
240 		function waitForDomLoaded() {
241 			// Check complete or interactive state if there is a body
242 			// element on some iframes IE 8 will produce a null body
243 			if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
244 				removeEvent(doc, "readystatechange", waitForDomLoaded);
245 				readyHandler();
246 			}
247 		}
248 
249 		function tryScroll() {
250 			try {
251 				// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
252 				// http://javascript.nwbox.com/IEContentLoaded/
253 				doc.documentElement.doScroll("left");
254 			} catch (ex) {
255 				setTimeout(tryScroll, 0);
256 				return;
257 			}
258 
259 			readyHandler();
260 		}
261 
262 		// Use W3C method
263 		if (doc.addEventListener) {
264 			if (doc.readyState === "complete") {
265 				readyHandler();
266 			} else {
267 				addEvent(win, 'DOMContentLoaded', readyHandler);
268 			}
269 		} else {
270 			// Use IE method
271 			addEvent(doc, "readystatechange", waitForDomLoaded);
272 
273 			// Wait until we can scroll, when we can the DOM is initialized
274 			if (doc.documentElement.doScroll && win.self === win.top) {
275 				tryScroll();
276 			}
277 		}
278 
279 		// Fallback if any of the above methods should fail for some odd reason
280 		addEvent(win, 'load', readyHandler);
281 	}
282 
283 	/**
284 	 * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
285 	 */
286 	function EventUtils() {
287 		var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
288 
289 		expando = eventExpandoPrefix + (+new Date()).toString(32);
290 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
291 		hasFocusIn = "onfocusin" in document.documentElement;
292 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
293 		count = 1;
294 
295 		// State if the DOMContentLoaded was executed or not
296 		self.domLoaded = false;
297 		self.events = events;
298 
299 		/**
300 		 * Executes all event handler callbacks for a specific event.
301 		 *
302 		 * @private
303 		 * @param {Event} evt Event object.
304 		 * @param {String} id Expando id value to look for.
305 		 */
306 		function executeHandlers(evt, id) {
307 			var callbackList, i, l, callback, container = events[id];
308 
309 			callbackList = container && container[evt.type];
310 			if (callbackList) {
311 				for (i = 0, l = callbackList.length; i < l; i++) {
312 					callback = callbackList[i];
313 
314 					// Check if callback exists might be removed if a unbind is called inside the callback
315 					if (callback && callback.func.call(callback.scope, evt) === false) {
316 						evt.preventDefault();
317 					}
318 
319 					// Should we stop propagation to immediate listeners
320 					if (evt.isImmediatePropagationStopped()) {
321 						return;
322 					}
323 				}
324 			}
325 		}
326 
327 		/**
328 		 * Binds a callback to an event on the specified target.
329 		 *
330 		 * @method bind
331 		 * @param {Object} target Target node/window or custom object.
332 		 * @param {String} names Name of the event to bind.
333 		 * @param {function} callback Callback function to execute when the event occurs.
334 		 * @param {Object} scope Scope to call the callback function on, defaults to target.
335 		 * @return {function} Callback function that got bound.
336 		 */
337 		self.bind = function(target, names, callback, scope) {
338 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
339 
340 			// Native event handler function patches the event and executes the callbacks for the expando
341 			function defaultNativeHandler(evt) {
342 				executeHandlers(fix(evt || win.event), id);
343 			}
344 
345 			// Don't bind to text nodes or comments
346 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
347 				return;
348 			}
349 
350 			// Create or get events id for the target
351 			if (!target[expando]) {
352 				id = count++;
353 				target[expando] = id;
354 				events[id] = {};
355 			} else {
356 				id = target[expando];
357 			}
358 
359 			// Setup the specified scope or use the target as a default
360 			scope = scope || target;
361 
362 			// Split names and bind each event, enables you to bind multiple events with one call
363 			names = names.split(' ');
364 			i = names.length;
365 			while (i--) {
366 				name = names[i];
367 				nativeHandler = defaultNativeHandler;
368 				fakeName = capture = false;
369 
370 				// Use ready instead of DOMContentLoaded
371 				if (name === "DOMContentLoaded") {
372 					name = "ready";
373 				}
374 
375 				// DOM is already ready
376 				if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
377 					callback.call(scope, fix({type: name}));
378 					continue;
379 				}
380 
381 				// Handle mouseenter/mouseleaver
382 				if (!hasMouseEnterLeave) {
383 					fakeName = mouseEnterLeave[name];
384 
385 					if (fakeName) {
386 						nativeHandler = function(evt) {
387 							var current, related;
388 
389 							current = evt.currentTarget;
390 							related = evt.relatedTarget;
391 
392 							// Check if related is inside the current target if it's not then the event should
393 							// be ignored since it's a mouseover/mouseout inside the element
394 							if (related && current.contains) {
395 								// Use contains for performance
396 								related = current.contains(related);
397 							} else {
398 								while (related && related !== current) {
399 									related = related.parentNode;
400 								}
401 							}
402 
403 							// Fire fake event
404 							if (!related) {
405 								evt = fix(evt || win.event);
406 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
407 								evt.target = current;
408 								executeHandlers(evt, id);
409 							}
410 						};
411 					}
412 				}
413 
414 				// Fake bubbeling of focusin/focusout
415 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
416 					capture = true;
417 					fakeName = name === "focusin" ? "focus" : "blur";
418 					nativeHandler = function(evt) {
419 						evt = fix(evt || win.event);
420 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
421 						executeHandlers(evt, id);
422 					};
423 				}
424 
425 				// Setup callback list and bind native event
426 				callbackList = events[id][name];
427 				if (!callbackList) {
428 					events[id][name] = callbackList = [{func: callback, scope: scope}];
429 					callbackList.fakeName = fakeName;
430 					callbackList.capture = capture;
431 					//callbackList.callback = callback;
432 
433 					// Add the nativeHandler to the callback list so that we can later unbind it
434 					callbackList.nativeHandler = nativeHandler;
435 
436 					// Check if the target has native events support
437 
438 					if (name === "ready") {
439 						bindOnReady(target, nativeHandler, self);
440 					} else {
441 						addEvent(target, fakeName || name, nativeHandler, capture);
442 					}
443 				} else {
444 					if (name === "ready" && self.domLoaded) {
445 						callback({type: name});
446 					} else {
447 						// If it already has an native handler then just push the callback
448 						callbackList.push({func: callback, scope: scope});
449 					}
450 				}
451 			}
452 
453 			target = callbackList = 0; // Clean memory for IE
454 
455 			return callback;
456 		};
457 
458 		/**
459 		 * Unbinds the specified event by name, name and callback or all events on the target.
460 		 *
461 		 * @method unbind
462 		 * @param {Object} target Target node/window or custom object.
463 		 * @param {String} names Optional event name to unbind.
464 		 * @param {function} callback Optional callback function to unbind.
465 		 * @return {EventUtils} Event utils instance.
466 		 */
467 		self.unbind = function(target, names, callback) {
468 			var id, callbackList, i, ci, name, eventMap;
469 
470 			// Don't bind to text nodes or comments
471 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
472 				return self;
473 			}
474 
475 			// Unbind event or events if the target has the expando
476 			id = target[expando];
477 			if (id) {
478 				eventMap = events[id];
479 
480 				// Specific callback
481 				if (names) {
482 					names = names.split(' ');
483 					i = names.length;
484 					while (i--) {
485 						name = names[i];
486 						callbackList = eventMap[name];
487 
488 						// Unbind the event if it exists in the map
489 						if (callbackList) {
490 							// Remove specified callback
491 							if (callback) {
492 								ci = callbackList.length;
493 								while (ci--) {
494 									if (callbackList[ci].func === callback) {
495 										var nativeHandler = callbackList.nativeHandler;
496 										var fakeName = callbackList.fakeName, capture = callbackList.capture;
497 
498 										// Clone callbackList since unbind inside a callback would otherwise break the handlers loop
499 										callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
500 										callbackList.nativeHandler = nativeHandler;
501 										callbackList.fakeName = fakeName;
502 										callbackList.capture = capture;
503 
504 										eventMap[name] = callbackList;
505 									}
506 								}
507 							}
508 
509 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
510 							if (!callback || callbackList.length === 0) {
511 								delete eventMap[name];
512 								removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
513 							}
514 						}
515 					}
516 				} else {
517 					// All events for a specific element
518 					for (name in eventMap) {
519 						callbackList = eventMap[name];
520 						removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
521 					}
522 
523 					eventMap = {};
524 				}
525 
526 				// Check if object is empty, if it isn't then we won't remove the expando map
527 				for (name in eventMap) {
528 					return self;
529 				}
530 
531 				// Delete event object
532 				delete events[id];
533 
534 				// Remove expando from target
535 				try {
536 					// IE will fail here since it can't delete properties from window
537 					delete target[expando];
538 				} catch (ex) {
539 					// IE will set it to null
540 					target[expando] = null;
541 				}
542 			}
543 
544 			return self;
545 		};
546 
547 		/**
548 		 * Fires the specified event on the specified target.
549 		 *
550 		 * @method fire
551 		 * @param {Object} target Target node/window or custom object.
552 		 * @param {String} name Event name to fire.
553 		 * @param {Object} args Optional arguments to send to the observers.
554 		 * @return {EventUtils} Event utils instance.
555 		 */
556 		self.fire = function(target, name, args) {
557 			var id;
558 
559 			// Don't bind to text nodes or comments
560 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
561 				return self;
562 			}
563 
564 			// Build event object by patching the args
565 			args = fix(null, args);
566 			args.type = name;
567 			args.target = target;
568 
569 			do {
570 				// Found an expando that means there is listeners to execute
571 				id = target[expando];
572 				if (id) {
573 					executeHandlers(args, id);
574 				}
575 
576 				// Walk up the DOM
577 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
578 			} while (target && !args.isPropagationStopped());
579 
580 			return self;
581 		};
582 
583 		/**
584 		 * Removes all bound event listeners for the specified target. This will also remove any bound
585 		 * listeners to child nodes within that target.
586 		 *
587 		 * @method clean
588 		 * @param {Object} target Target node/window object.
589 		 * @return {EventUtils} Event utils instance.
590 		 */
591 		self.clean = function(target) {
592 			var i, children, unbind = self.unbind;
593 
594 			// Don't bind to text nodes or comments
595 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
596 				return self;
597 			}
598 
599 			// Unbind any element on the specificed target
600 			if (target[expando]) {
601 				unbind(target);
602 			}
603 
604 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
605 			if (!target.getElementsByTagName) {
606 				target = target.document;
607 			}
608 
609 			// Remove events from each child element
610 			if (target && target.getElementsByTagName) {
611 				unbind(target);
612 
613 				children = target.getElementsByTagName('*');
614 				i = children.length;
615 				while (i--) {
616 					target = children[i];
617 
618 					if (target[expando]) {
619 						unbind(target);
620 					}
621 				}
622 			}
623 
624 			return self;
625 		};
626 
627 		/**
628 		 * Destroys the event object. Call this on IE to remove memory leaks.
629 		 */
630 		self.destroy = function() {
631 			events = {};
632 		};
633 
634 		// Legacy function for canceling events
635 		self.cancel = function(e) {
636 			if (e) {
637 				e.preventDefault();
638 				e.stopImmediatePropagation();
639 			}
640 
641 			return false;
642 		};
643 	}
644 
645 	EventUtils.Event = new EventUtils();
646 	EventUtils.Event.bind(window, 'ready', function() {});
647 
648 	return EventUtils;
649 });
650 
651 // Included from: js/tinymce/classes/dom/Sizzle.jQuery.js
652 
653 /**
654  * Sizzle.jQuery.js
655  *
656  * Copyright, Moxiecode Systems AB
657  * Released under LGPL License.
658  *
659  * License: http://www.tinymce.com/license
660  * Contributing: http://www.tinymce.com/contributing
661  */
662 
663 /*global jQuery:true */
664 
665 /*
666  * Fake Sizzle using jQuery.
667  */
668 define("tinymce/dom/Sizzle", [], function() {
669 	// Detect if jQuery is loaded
670 	if (!window.jQuery) {
671 		throw new Error("Load jQuery first");
672 	}
673 
674 	return jQuery.find;
675 });
676 
677 // Included from: js/tinymce/classes/util/Tools.js
678 
679 /**
680  * Tools.js
681  *
682  * Copyright, Moxiecode Systems AB
683  * Released under LGPL License.
684  *
685  * License: http://www.tinymce.com/license
686  * Contributing: http://www.tinymce.com/contributing
687  */
688 
689 /**
690  * This class contains various utlity functions. These are also exposed
691  * directly on the tinymce namespace.
692  *
693  * @class tinymce.util.Tools
694  */
695 define("tinymce/util/Tools", [], function() {
696 	/**
697 	 * Removes whitespace from the beginning and end of a string.
698 	 *
699 	 * @method trim
700 	 * @param {String} s String to remove whitespace from.
701 	 * @return {String} New string with removed whitespace.
702 	 */
703 	var whiteSpaceRegExp = /^\s*|\s*$/g;
704 
705 	function trim(str) {
706 		return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
707 	}
708 
709 	/**
710 	 * Returns true/false if the object is an array or not.
711 	 *
712 	 * @method isArray
713 	 * @param {Object} obj Object to check.
714 	 * @return {boolean} true/false state if the object is an array or not.
715 	 */
716 	var isArray = Array.isArray || function(obj) {
717 		return Object.prototype.toString.call(obj) === "[object Array]";
718 	};
719 
720 	/**
721 	 * Checks if a object is of a specific type for example an array.
722 	 *
723 	 * @method is
724 	 * @param {Object} o Object to check type of.
725 	 * @param {string} t Optional type to check for.
726 	 * @return {Boolean} true/false if the object is of the specified type.
727 	 */
728 	function is(o, t) {
729 		if (!t) {
730 			return o !== undefined;
731 		}
732 
733 		if (t == 'array' && isArray(o)) {
734 			return true;
735 		}
736 
737 		return typeof(o) == t;
738 	}
739 
740 	/**
741 	 * Converts the specified object into a real JavaScript array.
742 	 *
743 	 * @method toArray
744 	 * @param {Object} obj Object to convert into array.
745 	 * @return {Array} Array object based in input.
746 	 */
747 	function toArray(obj) {
748 		var array = obj, i, l;
749 
750 		if (!isArray(obj)) {
751 			array = [];
752 			for (i = 0, l = obj.length; i < l; i++) {
753 				array[i] = obj[i];
754 			}
755 		}
756 
757 		return array;
758 	}
759 
760 	/**
761 	 * Makes a name/object map out of an array with names.
762 	 *
763 	 * @method makeMap
764 	 * @param {Array/String} items Items to make map out of.
765 	 * @param {String} delim Optional delimiter to split string by.
766 	 * @param {Object} map Optional map to add items to.
767 	 * @return {Object} Name/value map of items.
768 	 */
769 	function makeMap(items, delim, map) {
770 		var i;
771 
772 		items = items || [];
773 		delim = delim || ',';
774 
775 		if (typeof(items) == "string") {
776 			items = items.split(delim);
777 		}
778 
779 		map = map || {};
780 
781 		i = items.length;
782 		while (i--) {
783 			map[items[i]] = {};
784 		}
785 
786 		return map;
787 	}
788 
789 	/**
790 	 * Performs an iteration of all items in a collection such as an object or array. This method will execure the
791 	 * callback function for each item in the collection, if the callback returns false the iteration will terminate.
792 	 * The callback has the following format: cb(value, key_or_index).
793 	 *
794 	 * @method each
795 	 * @param {Object} o Collection to iterate.
796 	 * @param {function} cb Callback function to execute for each item.
797 	 * @param {Object} s Optional scope to execute the callback in.
798 	 * @example
799 	 * // Iterate an array
800 	 * tinymce.each([1,2,3], function(v, i) {
801 	 *     console.debug("Value: " + v + ", Index: " + i);
802 	 * });
803 	 *
804 	 * // Iterate an object
805 	 * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
806 	 *     console.debug("Value: " + v + ", Key: " + k);
807 	 * });
808 	 */
809 	function each(o, cb, s) {
810 		var n, l;
811 
812 		if (!o) {
813 			return 0;
814 		}
815 
816 		s = s || o;
817 
818 		if (o.length !== undefined) {
819 			// Indexed arrays, needed for Safari
820 			for (n = 0, l = o.length; n < l; n++) {
821 				if (cb.call(s, o[n], n, o) === false) {
822 					return 0;
823 				}
824 			}
825 		} else {
826 			// Hashtables
827 			for (n in o) {
828 				if (o.hasOwnProperty(n)) {
829 					if (cb.call(s, o[n], n, o) === false) {
830 						return 0;
831 					}
832 				}
833 			}
834 		}
835 
836 		return 1;
837 	}
838 
839 	/**
840 	 * Creates a new array by the return value of each iteration function call. This enables you to convert
841 	 * one array list into another.
842 	 *
843 	 * @method map
844 	 * @param {Array} a Array of items to iterate.
845 	 * @param {function} f Function to call for each item. It's return value will be the new value.
846 	 * @return {Array} Array with new values based on function return values.
847 	 */
848 	function map(a, f) {
849 		var o = [];
850 
851 		each(a, function(v) {
852 			o.push(f(v));
853 		});
854 
855 		return o;
856 	}
857 
858 	/**
859 	 * Filters out items from the input array by calling the specified function for each item.
860 	 * If the function returns false the item will be excluded if it returns true it will be included.
861 	 *
862 	 * @method grep
863 	 * @param {Array} a Array of items to loop though.
864 	 * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
865 	 * @return {Array} New array with values imported and filtered based in input.
866 	 * @example
867 	 * // Filter out some items, this will return an array with 4 and 5
868 	 * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
869 	 */
870 	function grep(a, f) {
871 		var o = [];
872 
873 		each(a, function(v) {
874 			if (!f || f(v)) {
875 				o.push(v);
876 			}
877 		});
878 
879 		return o;
880 	}
881 
882 	/**
883 	 * Creates a class, subclass or static singleton.
884 	 * More details on this method can be found in the Wiki.
885 	 *
886 	 * @method create
887 	 * @param {String} s Class name, inheritage and prefix.
888 	 * @param {Object} p Collection of methods to add to the class.
889 	 * @param {Object} root Optional root object defaults to the global window object.
890 	 * @example
891 	 * // Creates a basic class
892 	 * tinymce.create('tinymce.somepackage.SomeClass', {
893 	 *     SomeClass: function() {
894 	 *         // Class constructor
895 	 *     },
896 	 *
897 	 *     method: function() {
898 	 *         // Some method
899 	 *     }
900 	 * });
901 	 *
902 	 * // Creates a basic subclass class
903 	 * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
904 	 *     SomeSubClass: function() {
905 	 *         // Class constructor
906 	 *         this.parent(); // Call parent constructor
907 	 *     },
908 	 *
909 	 *     method: function() {
910 	 *         // Some method
911 	 *         this.parent(); // Call parent method
912 	 *     },
913 	 *
914 	 *     'static': {
915 	 *         staticMethod: function() {
916 	 *             // Static method
917 	 *         }
918 	 *     }
919 	 * });
920 	 *
921 	 * // Creates a singleton/static class
922 	 * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
923 	 *     method: function() {
924 	 *         // Some method
925 	 *     }
926 	 * });
927 	 */
928 	function create(s, p, root) {
929 		var self = this, sp, ns, cn, scn, c, de = 0;
930 
931 		// Parse : <prefix> <class>:<super class>
932 		s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
933 		cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
934 
935 		// Create namespace for new class
936 		ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
937 
938 		// Class already exists
939 		if (ns[cn]) {
940 			return;
941 		}
942 
943 		// Make pure static class
944 		if (s[2] == 'static') {
945 			ns[cn] = p;
946 
947 			if (this.onCreate) {
948 				this.onCreate(s[2], s[3], ns[cn]);
949 			}
950 
951 			return;
952 		}
953 
954 		// Create default constructor
955 		if (!p[cn]) {
956 			p[cn] = function() {};
957 			de = 1;
958 		}
959 
960 		// Add constructor and methods
961 		ns[cn] = p[cn];
962 		self.extend(ns[cn].prototype, p);
963 
964 		// Extend
965 		if (s[5]) {
966 			sp = self.resolve(s[5]).prototype;
967 			scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
968 
969 			// Extend constructor
970 			c = ns[cn];
971 			if (de) {
972 				// Add passthrough constructor
973 				ns[cn] = function() {
974 					return sp[scn].apply(this, arguments);
975 				};
976 			} else {
977 				// Add inherit constructor
978 				ns[cn] = function() {
979 					this.parent = sp[scn];
980 					return c.apply(this, arguments);
981 				};
982 			}
983 			ns[cn].prototype[cn] = ns[cn];
984 
985 			// Add super methods
986 			self.each(sp, function(f, n) {
987 				ns[cn].prototype[n] = sp[n];
988 			});
989 
990 			// Add overridden methods
991 			self.each(p, function(f, n) {
992 				// Extend methods if needed
993 				if (sp[n]) {
994 					ns[cn].prototype[n] = function() {
995 						this.parent = sp[n];
996 						return f.apply(this, arguments);
997 					};
998 				} else {
999 					if (n != cn) {
1000 						ns[cn].prototype[n] = f;
1001 					}
1002 				}
1003 			});
1004 		}
1005 
1006 		// Add static methods
1007 		/*jshint sub:true*/
1008 		self.each(p['static'], function(f, n) {
1009 			ns[cn][n] = f;
1010 		});
1011 	}
1012 
1013 	/**
1014 	 * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
1015 	 *
1016 	 * @method inArray
1017 	 * @param {Array} a Array/Object to search for value in.
1018 	 * @param {Object} v Value to check for inside the array.
1019 	 * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
1020 	 * @example
1021 	 * // Get index of value in array this will alert 1 since 2 is at that index
1022 	 * alert(tinymce.inArray([1,2,3], 2));
1023 	 */
1024 	function inArray(a, v) {
1025 		var i, l;
1026 
1027 		if (a) {
1028 			for (i = 0, l = a.length; i < l; i++) {
1029 				if (a[i] === v) {
1030 					return i;
1031 				}
1032 			}
1033 		}
1034 
1035 		return -1;
1036 	}
1037 
1038 	function extend(obj, ext) {
1039 		var i, l, name, args = arguments, value;
1040 
1041 		for (i = 1, l = args.length; i < l; i++) {
1042 			ext = args[i];
1043 			for (name in ext) {
1044 				if (ext.hasOwnProperty(name)) {
1045 					value = ext[name];
1046 
1047 					if (value !== undefined) {
1048 						obj[name] = value;
1049 					}
1050 				}
1051 			}
1052 		}
1053 
1054 		return obj;
1055 	}
1056 
1057 	/**
1058 	 * Executed the specified function for each item in a object tree.
1059 	 *
1060 	 * @method walk
1061 	 * @param {Object} o Object tree to walk though.
1062 	 * @param {function} f Function to call for each item.
1063 	 * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
1064 	 * @param {String} s Optional scope to execute the function in.
1065 	 */
1066 	function walk(o, f, n, s) {
1067 		s = s || this;
1068 
1069 		if (o) {
1070 			if (n) {
1071 				o = o[n];
1072 			}
1073 
1074 			each(o, function(o, i) {
1075 				if (f.call(s, o, i, n) === false) {
1076 					return false;
1077 				}
1078 
1079 				walk(o, f, n, s);
1080 			});
1081 		}
1082 	}
1083 
1084 	/**
1085 	 * Creates a namespace on a specific object.
1086 	 *
1087 	 * @method createNS
1088 	 * @param {String} n Namespace to create for example a.b.c.d.
1089 	 * @param {Object} o Optional object to add namespace to, defaults to window.
1090 	 * @return {Object} New namespace object the last item in path.
1091 	 * @example
1092 	 * // Create some namespace
1093 	 * tinymce.createNS('tinymce.somepackage.subpackage');
1094 	 *
1095 	 * // Add a singleton
1096 	 * var tinymce.somepackage.subpackage.SomeSingleton = {
1097 	 *     method: function() {
1098 	 *         // Some method
1099 	 *     }
1100 	 * };
1101 	 */
1102 	function createNS(n, o) {
1103 		var i, v;
1104 
1105 		o = o || window;
1106 
1107 		n = n.split('.');
1108 		for (i = 0; i < n.length; i++) {
1109 			v = n[i];
1110 
1111 			if (!o[v]) {
1112 				o[v] = {};
1113 			}
1114 
1115 			o = o[v];
1116 		}
1117 
1118 		return o;
1119 	}
1120 
1121 	/**
1122 	 * Resolves a string and returns the object from a specific structure.
1123 	 *
1124 	 * @method resolve
1125 	 * @param {String} n Path to resolve for example a.b.c.d.
1126 	 * @param {Object} o Optional object to search though, defaults to window.
1127 	 * @return {Object} Last object in path or null if it couldn't be resolved.
1128 	 * @example
1129 	 * // Resolve a path into an object reference
1130 	 * var obj = tinymce.resolve('a.b.c.d');
1131 	 */
1132 	function resolve(n, o) {
1133 		var i, l;
1134 
1135 		o = o || window;
1136 
1137 		n = n.split('.');
1138 		for (i = 0, l = n.length; i < l; i++) {
1139 			o = o[n[i]];
1140 
1141 			if (!o) {
1142 				break;
1143 			}
1144 		}
1145 
1146 		return o;
1147 	}
1148 
1149 	/**
1150 	 * Splits a string but removes the whitespace before and after each value.
1151 	 *
1152 	 * @method explode
1153 	 * @param {string} s String to split.
1154 	 * @param {string} d Delimiter to split by.
1155 	 * @example
1156 	 * // Split a string into an array with a,b,c
1157 	 * var arr = tinymce.explode('a, b,   c');
1158 	 */
1159 	function explode(s, d) {
1160 		if (!s || is(s, 'array')) {
1161 			return s;
1162 		}
1163 
1164 		return map(s.split(d || ','), trim);
1165 	}
1166 
1167 	return {
1168 		trim: trim,
1169 		isArray: isArray,
1170 		is: is,
1171 		toArray: toArray,
1172 		makeMap: makeMap,
1173 		each: each,
1174 		map: map,
1175 		grep: grep,
1176 		inArray: inArray,
1177 		extend: extend,
1178 		create: create,
1179 		walk: walk,
1180 		createNS: createNS,
1181 		resolve: resolve,
1182 		explode: explode
1183 	};
1184 });
1185 
1186 // Included from: js/tinymce/classes/Env.js
1187 
1188 /**
1189  * Env.js
1190  *
1191  * Copyright, Moxiecode Systems AB
1192  * Released under LGPL License.
1193  *
1194  * License: http://www.tinymce.com/license
1195  * Contributing: http://www.tinymce.com/contributing
1196  */
1197 
1198 /**
1199  * This class contains various environment constants like browser versions etc.
1200  * Normally you don't want to sniff specific browser versions but sometimes you have
1201  * to when it's impossible to feature detect. So use this with care.
1202  *
1203  * @class tinymce.Env
1204  * @static
1205  */
1206 define("tinymce/Env", [], function() {
1207 	var nav = navigator, userAgent = nav.userAgent;
1208 	var opera, webkit, ie, ie11, gecko, mac, iDevice;
1209 
1210 	opera = window.opera && window.opera.buildNumber;
1211 	webkit = /WebKit/.test(userAgent);
1212 	ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
1213 	ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
1214 	ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
1215 	ie = ie || ie11;
1216 	gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
1217 	mac = userAgent.indexOf('Mac') != -1;
1218 	iDevice = /(iPad|iPhone)/.test(userAgent);
1219 
1220 	// Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
1221 	// says it has contentEditable support but there is no visible caret.
1222 	var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
1223 
1224 	return {
1225 		/**
1226 		 * Constant that is true if the browser is Opera.
1227 		 *
1228 		 * @property opera
1229 		 * @type Boolean
1230 		 * @final
1231 		 */
1232 		opera: opera,
1233 
1234 		/**
1235 		 * Constant that is true if the browser is WebKit (Safari/Chrome).
1236 		 *
1237 		 * @property webKit
1238 		 * @type Boolean
1239 		 * @final
1240 		 */
1241 		webkit: webkit,
1242 
1243 		/**
1244 		 * Constant that is more than zero if the browser is IE.
1245 		 *
1246 		 * @property ie
1247 		 * @type Boolean
1248 		 * @final
1249 		 */
1250 		ie: ie,
1251 
1252 		/**
1253 		 * Constant that is true if the browser is Gecko.
1254 		 *
1255 		 * @property gecko
1256 		 * @type Boolean
1257 		 * @final
1258 		 */
1259 		gecko: gecko,
1260 
1261 		/**
1262 		 * Constant that is true if the os is Mac OS.
1263 		 *
1264 		 * @property mac
1265 		 * @type Boolean
1266 		 * @final
1267 		 */
1268 		mac: mac,
1269 
1270 		/**
1271 		 * Constant that is true if the os is iOS.
1272 		 *
1273 		 * @property iOS
1274 		 * @type Boolean
1275 		 * @final
1276 		 */
1277 		iOS: iDevice,
1278 
1279 		/**
1280 		 * Constant that is true if the browser supports editing.
1281 		 *
1282 		 * @property contentEditable
1283 		 * @type Boolean
1284 		 * @final
1285 		 */
1286 		contentEditable: contentEditable,
1287 
1288 		/**
1289 		 * Transparent image data url.
1290 		 *
1291 		 * @property transparentSrc
1292 		 * @type Boolean
1293 		 * @final
1294 		 */
1295 		transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
1296 
1297 		/**
1298 		 * Returns true/false if the browser can or can't place the caret after a inline block like an image.
1299 		 *
1300 		 * @property noCaretAfter
1301 		 * @type Boolean
1302 		 * @final
1303 		 */
1304 		caretAfter: ie != 8,
1305 
1306 		/**
1307 		 * Constant that is true if the browser supports native DOM Ranges. IE 9+.
1308 		 *
1309 		 * @property range
1310 		 * @type Boolean
1311 		 */
1312 		range: window.getSelection && "Range" in window,
1313 
1314 		/**
1315 		 * Returns the IE document mode for non IE browsers this will fake IE 10.
1316 		 *
1317 		 * @property documentMode
1318 		 * @type Number
1319 		 */
1320 		documentMode: ie ? (document.documentMode || 7) : 10
1321 	};
1322 });
1323 
1324 // Included from: js/tinymce/classes/dom/DomQuery.js
1325 
1326 /**
1327  * DomQuery.js
1328  *
1329  * Copyright, Moxiecode Systems AB
1330  * Released under LGPL License.
1331  *
1332  * License: http://www.tinymce.com/license
1333  * Contributing: http://www.tinymce.com/contributing
1334  */
1335 
1336 /**
1337  * This class mimics most of the jQuery API:
1338  *
1339  * This is whats currently implemented:
1340  * - Utility functions
1341  * - DOM traversial
1342  * - DOM manipulation
1343  * - Event binding
1344  *
1345  * This is not currently implemented:
1346  * - Dimension
1347  * - Ajax
1348  * - Animation
1349  * - Advanced chaining
1350  *
1351  * @example
1352  * var $ = tinymce.dom.DomQuery;
1353  * $('p').attr('attr', 'value').addClass('class');
1354  *
1355  * @class tinymce.dom.DomQuery
1356  */
1357 define("tinymce/dom/DomQuery", [
1358 	"tinymce/dom/EventUtils",
1359 	"tinymce/dom/Sizzle",
1360 	"tinymce/util/Tools",
1361 	"tinymce/Env"
1362 ], function(EventUtils, Sizzle, Tools, Env) {
1363 	var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
1364 	var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
1365 	var Event = EventUtils.Event, undef;
1366 
1367 	function isDefined(obj) {
1368 		return typeof obj !== 'undefined';
1369 	}
1370 
1371 	function isString(obj) {
1372 		return typeof obj === 'string';
1373 	}
1374 
1375 	function createFragment(html, fragDoc) {
1376 		var frag, node, container;
1377 
1378 		fragDoc = fragDoc || doc;
1379 		container = fragDoc.createElement('div');
1380 		frag = fragDoc.createDocumentFragment();
1381 		container.innerHTML = html;
1382 
1383 		while ((node = container.firstChild)) {
1384 			frag.appendChild(node);
1385 		}
1386 
1387 		return frag;
1388 	}
1389 
1390 	function domManipulate(targetNodes, sourceItem, callback, reverse) {
1391 		var i;
1392 
1393 		if (isString(sourceItem)) {
1394 			sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
1395 		} else if (sourceItem.length && !sourceItem.nodeType) {
1396 			sourceItem = DomQuery.makeArray(sourceItem);
1397 
1398 			if (reverse) {
1399 				for (i = sourceItem.length - 1; i >= 0; i--) {
1400 					domManipulate(targetNodes, sourceItem[i], callback, reverse);
1401 				}
1402 			} else {
1403 				for (i = 0; i < sourceItem.length; i++) {
1404 					domManipulate(targetNodes, sourceItem[i], callback, reverse);
1405 				}
1406 			}
1407 
1408 			return targetNodes;
1409 		}
1410 
1411 		if (sourceItem.nodeType) {
1412 			i = targetNodes.length;
1413 			while (i--) {
1414 				callback.call(targetNodes[i], sourceItem);
1415 			}
1416 		}
1417 
1418 		return targetNodes;
1419 	}
1420 
1421 	function hasClass(node, className) {
1422 		return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
1423 	}
1424 
1425 	function wrap(elements, wrapper, all) {
1426 		var lastParent, newWrapper;
1427 
1428 		wrapper = DomQuery(wrapper)[0];
1429 
1430 		elements.each(function() {
1431 			var self = this;
1432 
1433 			if (!all || lastParent != self.parentNode) {
1434 				lastParent = self.parentNode;
1435 				newWrapper = wrapper.cloneNode(false);
1436 				self.parentNode.insertBefore(newWrapper, self);
1437 				newWrapper.appendChild(self);
1438 			} else {
1439 				newWrapper.appendChild(self);
1440 			}
1441 		});
1442 
1443 		return elements;
1444 	}
1445 
1446 	var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
1447 	var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected', ' ');
1448 	var propFix = {
1449 		'for': 'htmlFor',
1450 		'class': 'className',
1451 		'readonly': 'readOnly'
1452 	};
1453 	var cssFix = {
1454 		'float': 'cssFloat'
1455 	};
1456 
1457 	var attrHooks = {}, cssHooks = {};
1458 
1459 	function DomQuery(selector, context) {
1460 		/*eslint new-cap:0 */
1461 		return new DomQuery.fn.init(selector, context);
1462 	}
1463 
1464 	function inArray(item, array) {
1465 		var i;
1466 
1467 		if (array.indexOf) {
1468 			return array.indexOf(item);
1469 		}
1470 
1471 		i = array.length;
1472 		while (i--) {
1473 			if (array[i] === item) {
1474 				return i;
1475 			}
1476 		}
1477 
1478 		return -1;
1479 	}
1480 
1481 	var whiteSpaceRegExp = /^\s*|\s*$/g;
1482 
1483 	function trim(str) {
1484 		return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
1485 	}
1486 
1487 	function each(obj, callback) {
1488 		var length, key, i, undef, value;
1489 
1490 		if (obj) {
1491 			length = obj.length;
1492 
1493 			if (length === undef) {
1494 				// Loop object items
1495 				for (key in obj) {
1496 					if (obj.hasOwnProperty(key)) {
1497 						value = obj[key];
1498 						if (callback.call(value, key, value) === false) {
1499 							break;
1500 						}
1501 					}
1502 				}
1503 			} else {
1504 				// Loop array items
1505 				for (i = 0; i < length; i++) {
1506 					value = obj[i];
1507 					if (callback.call(value, i, value) === false) {
1508 						break;
1509 					}
1510 				}
1511 			}
1512 		}
1513 
1514 		return obj;
1515 	}
1516 
1517 	function grep(array, callback) {
1518 		var out = [];
1519 
1520 		each(array, function(i, item) {
1521 			if (callback(item, i)) {
1522 				out.push(item);
1523 			}
1524 		});
1525 
1526 		return out;
1527 	}
1528 
1529 	function getElementDocument(element) {
1530 		if (!element) {
1531 			return doc;
1532 		}
1533 
1534 		if (element.nodeType == 9) {
1535 			return element;
1536 		}
1537 
1538 		return element.ownerDocument;
1539 	}
1540 
1541 	DomQuery.fn = DomQuery.prototype = {
1542 		constructor: DomQuery,
1543 
1544 		/**
1545 		 * Selector for the current set.
1546 		 *
1547 		 * @property selector
1548 		 * @type String
1549 		 */
1550 		selector: "",
1551 
1552 		/**
1553 		 * Context used to create the set.
1554 		 *
1555 		 * @property context
1556 		 * @type Element
1557 		 */
1558 		context: null,
1559 
1560 		/**
1561 		 * Number of items in the current set.
1562 		 *
1563 		 * @property length
1564 		 * @type Number
1565 		 */
1566 		length: 0,
1567 
1568 		/**
1569 		 * Constructs a new DomQuery instance with the specified selector or context.
1570 		 *
1571 		 * @constructor
1572 		 * @method init
1573 		 * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string.
1574 		 * @param {Document/Element} context Optional context to search in.
1575 		 */
1576 		init: function(selector, context) {
1577 			var self = this, match, node;
1578 
1579 			if (!selector) {
1580 				return self;
1581 			}
1582 
1583 			if (selector.nodeType) {
1584 				self.context = self[0] = selector;
1585 				self.length = 1;
1586 
1587 				return self;
1588 			}
1589 
1590 			if (context && context.nodeType) {
1591 				self.context = context;
1592 			} else {
1593 				if (context) {
1594 					return DomQuery(selector).attr(context);
1595 				} else {
1596 					self.context = context = document;
1597 				}
1598 			}
1599 
1600 			if (isString(selector)) {
1601 				self.selector = selector;
1602 
1603 				if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
1604 					match = [null, selector, null];
1605 				} else {
1606 					match = rquickExpr.exec(selector);
1607 				}
1608 
1609 				if (match) {
1610 					if (match[1]) {
1611 						node = createFragment(selector, getElementDocument(context)).firstChild;
1612 
1613 						while (node) {
1614 							push.call(self, node);
1615 							node = node.nextSibling;
1616 						}
1617 					} else {
1618 						node = getElementDocument(context).getElementById(match[2]);
1619 
1620 						if (!node) {
1621 							return self;
1622 						}
1623 
1624 						if (node.id !== match[2]) {
1625 							return self.find(selector);
1626 						}
1627 
1628 						self.length = 1;
1629 						self[0] = node;
1630 					}
1631 				} else {
1632 					return DomQuery(context).find(selector);
1633 				}
1634 			} else {
1635 				this.add(selector, false);
1636 			}
1637 
1638 			return self;
1639 		},
1640 
1641 		/**
1642 		 * Converts the current set to an array.
1643 		 *
1644 		 * @method toArray
1645 		 * @param {Array} Array of all nodes in set.
1646 		 */
1647 		toArray: function() {
1648 			return Tools.toArray(this);
1649 		},
1650 
1651 		/**
1652 		 * Adds new nodes to the set.
1653 		 *
1654 		 * @method add
1655 		 * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set.
1656 		 * @return {tinymce.dom.DomQuery} New instance with nodes added.
1657 		 */
1658 		add: function(items, sort) {
1659 			var self = this, nodes, i;
1660 
1661 			if (isString(items)) {
1662 				return self.add(DomQuery(items));
1663 			}
1664 
1665 			if (items.nodeType) {
1666 				return self.add([items]);
1667 			}
1668 
1669 			if (sort !== false) {
1670 				nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items)));
1671 				self.length = nodes.length;
1672 				for (i = 0; i < nodes.length; i++) {
1673 					self[i] = nodes[i];
1674 				}
1675 			} else {
1676 				push.apply(self, DomQuery.makeArray(items));
1677 			}
1678 
1679 			return self;
1680 		},
1681 
1682 		/**
1683 		 * Sets/gets attributes on the elements in the current set.
1684 		 *
1685 		 * @method attr
1686 		 * @param {String/Object} name Name of attribute to get or an object with attributes to set.
1687 		 * @param {String} value Optional value to set.
1688 		 * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified.
1689 		 */
1690 		attr: function(name, value) {
1691 			var self = this, hook;
1692 
1693 			if (typeof name === "object") {
1694 				each(name, function(name, value) {
1695 					self.attr(name, value);
1696 				});
1697 			} else if (isDefined(value)) {
1698 				this.each(function() {
1699 					var hook;
1700 
1701 					if (this.nodeType === 1) {
1702 						hook = attrHooks[name];
1703 						if (hook && hook.set) {
1704 							hook.set(this, value);
1705 							return;
1706 						}
1707 
1708 						if (value === null) {
1709 							this.removeAttribute(name, 2);
1710 						} else {
1711 							this.setAttribute(name, value, 2);
1712 						}
1713 					}
1714 				});
1715 			} else {
1716 				if (self[0] && self[0].nodeType === 1) {
1717 					hook = attrHooks[name];
1718 					if (hook && hook.get) {
1719 						return hook.get(self[0], name);
1720 					}
1721 
1722 					if (booleanMap[name]) {
1723 						return self.prop(name) ? name : undef;
1724 					}
1725 
1726 					value = self[0].getAttribute(name, 2);
1727 
1728 					if (value === null) {
1729 						value = undef;
1730 					}
1731 				}
1732 
1733 				return value;
1734 			}
1735 
1736 			return self;
1737 		},
1738 
1739 		/**
1740 		 * Removes attributse on the elements in the current set.
1741 		 *
1742 		 * @method removeAttr
1743 		 * @param {String/Object} name Name of attribute to remove.
1744 		 * @return {tinymce.dom.DomQuery/String} Current set.
1745 		 */
1746 		removeAttr: function(name) {
1747 			return this.attr(name, null);
1748 		},
1749 
1750 		/**
1751 		 * Sets/gets properties on the elements in the current set.
1752 		 *
1753 		 * @method attr
1754 		 * @param {String/Object} name Name of property to get or an object with properties to set.
1755 		 * @param {String} value Optional value to set.
1756 		 * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified.
1757 		 */
1758 		prop: function(name, value) {
1759 			var self = this;
1760 
1761 			name = propFix[name] || name;
1762 
1763 			if (typeof name === "object") {
1764 				each(name, function(name, value) {
1765 					self.prop(name, value);
1766 				});
1767 			} else if (isDefined(value)) {
1768 				this.each(function() {
1769 					if (this.nodeType == 1) {
1770 						this[name] = value;
1771 					}
1772 				});
1773 			} else {
1774 				if (self[0] && self[0].nodeType && name in self[0]) {
1775 					return self[0][name];
1776 				}
1777 
1778 				return value;
1779 			}
1780 
1781 			return self;
1782 		},
1783 
1784 		/**
1785 		 * Sets/gets styles on the elements in the current set.
1786 		 *
1787 		 * @method css
1788 		 * @param {String/Object} name Name of style to get or an object with styles to set.
1789 		 * @param {String} value Optional value to set.
1790 		 * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified.
1791 		 */
1792 		css: function(name, value) {
1793 			var self = this, elm, hook;
1794 
1795 			function camel(name) {
1796 				return name.replace(/-(\D)/g, function(a, b) {
1797 					return b.toUpperCase();
1798 				});
1799 			}
1800 
1801 			function dashed(name) {
1802 				return name.replace(/[A-Z]/g, function(a) {
1803 					return '-' + a;
1804 				});
1805 			}
1806 
1807 			if (typeof name === "object") {
1808 				each(name, function(name, value) {
1809 					self.css(name, value);
1810 				});
1811 			} else {
1812 				if (isDefined(value)) {
1813 					name = camel(name);
1814 
1815 					// Default px suffix on these
1816 					if (typeof(value) === 'number' && !numericCssMap[name]) {
1817 						value += 'px';
1818 					}
1819 
1820 					self.each(function() {
1821 						var style = this.style;
1822 
1823 						hook = cssHooks[name];
1824 						if (hook && hook.set) {
1825 							hook.set(this, value);
1826 							return;
1827 						}
1828 
1829 						try {
1830 							this.style[cssFix[name] || name] = value;
1831 						} catch (ex) {
1832 							// Ignore
1833 						}
1834 
1835 						if (value === null || value === '') {
1836 							if (style.removeProperty) {
1837 								style.removeProperty(dashed(name));
1838 							} else {
1839 								style.removeAttribute(name);
1840 							}
1841 						}
1842 					});
1843 				} else {
1844 					elm = self[0];
1845 
1846 					hook = cssHooks[name];
1847 					if (hook && hook.get) {
1848 						return hook.get(elm);
1849 					}
1850 
1851 					if (elm.ownerDocument.defaultView) {
1852 						try {
1853 							return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
1854 						} catch (ex) {
1855 							return undef;
1856 						}
1857 					} else if (elm.currentStyle) {
1858 						return elm.currentStyle[camel(name)];
1859 					}
1860 				}
1861 			}
1862 
1863 			return self;
1864 		},
1865 
1866 		/**
1867 		 * Removes all nodes in set from the document.
1868 		 *
1869 		 * @method remove
1870 		 * @return {tinymce.dom.DomQuery} Current set with the removed nodes.
1871 		 */
1872 		remove: function() {
1873 			var self = this, node, i = this.length;
1874 
1875 			while (i--) {
1876 				node = self[i];
1877 				Event.clean(node);
1878 
1879 				if (node.parentNode) {
1880 					node.parentNode.removeChild(node);
1881 				}
1882 			}
1883 
1884 			return this;
1885 		},
1886 
1887 		/**
1888 		 * Empties all elements in set.
1889 		 *
1890 		 * @method empty
1891 		 * @return {tinymce.dom.DomQuery} Current set with the empty nodes.
1892 		 */
1893 		empty: function() {
1894 			var self = this, node, i = this.length;
1895 
1896 			while (i--) {
1897 				node = self[i];
1898 				while (node.firstChild) {
1899 					node.removeChild(node.firstChild);
1900 				}
1901 			}
1902 
1903 			return this;
1904 		},
1905 
1906 		/**
1907 		 * Sets or gets the HTML of the current set or first set node.
1908 		 *
1909 		 * @method html
1910 		 * @param {String} value Optional innerHTML value to set on each element.
1911 		 * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element.
1912 		 */
1913 		html: function(value) {
1914 			var self = this, i;
1915 
1916 			if (isDefined(value)) {
1917 				i = self.length;
1918 
1919 				try {
1920 					while (i--) {
1921 						self[i].innerHTML = value;
1922 					}
1923 				} catch (ex) {
1924 					// Workaround for "Unknown runtime error" when DIV is added to P on IE
1925 					DomQuery(self[i]).empty().append(value);
1926 				}
1927 
1928 				return self;
1929 			}
1930 
1931 			return self[0] ? self[0].innerHTML : '';
1932 		},
1933 
1934 		/**
1935 		 * Sets or gets the text of the current set or first set node.
1936 		 *
1937 		 * @method text
1938 		 * @param {String} value Optional innerText value to set on each element.
1939 		 * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element.
1940 		 */
1941 		text: function(value) {
1942 			var self = this, i;
1943 
1944 			if (isDefined(value)) {
1945 				i = self.length;
1946 				while (i--) {
1947 					if ("innerText" in self[i]) {
1948 						self[i].innerText = value;
1949 					} else {
1950 						self[0].textContent = value;
1951 					}
1952 				}
1953 
1954 				return self;
1955 			}
1956 
1957 			return self[0] ? (self[0].innerText || self[0].textContent) : '';
1958 		},
1959 
1960 		/**
1961 		 * Appends the specified node/html or node set to the current set nodes.
1962 		 *
1963 		 * @method append
1964 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set.
1965 		 * @return {tinymce.dom.DomQuery} Current set.
1966 		 */
1967 		append: function() {
1968 			return domManipulate(this, arguments, function(node) {
1969 				if (this.nodeType === 1) {
1970 					this.appendChild(node);
1971 				}
1972 			});
1973 		},
1974 
1975 		/**
1976 		 * Prepends the specified node/html or node set to the current set nodes.
1977 		 *
1978 		 * @method prepend
1979 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to prepend to each element in set.
1980 		 * @return {tinymce.dom.DomQuery} Current set.
1981 		 */
1982 		prepend: function() {
1983 			return domManipulate(this, arguments, function(node) {
1984 				if (this.nodeType === 1) {
1985 					this.insertBefore(node, this.firstChild);
1986 				}
1987 			}, true);
1988 		},
1989 
1990 		/**
1991 		 * Adds the specified elements before current set nodes.
1992 		 *
1993 		 * @method before
1994 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set.
1995 		 * @return {tinymce.dom.DomQuery} Current set.
1996 		 */
1997 		before: function() {
1998 			var self = this;
1999 
2000 			if (self[0] && self[0].parentNode) {
2001 				return domManipulate(self, arguments, function(node) {
2002 					this.parentNode.insertBefore(node, this);
2003 				});
2004 			}
2005 
2006 			return self;
2007 		},
2008 
2009 		/**
2010 		 * Adds the specified elements after current set nodes.
2011 		 *
2012 		 * @method after
2013 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set.
2014 		 * @return {tinymce.dom.DomQuery} Current set.
2015 		 */
2016 		after: function() {
2017 			var self = this;
2018 
2019 			if (self[0] && self[0].parentNode) {
2020 				return domManipulate(self, arguments, function(node) {
2021 					this.parentNode.insertBefore(node, this.nextSibling);
2022 				}, true);
2023 			}
2024 
2025 			return self;
2026 		},
2027 
2028 		/**
2029 		 * Appends the specified set nodes to the specified selector/instance.
2030 		 *
2031 		 * @method appendTo
2032 		 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to.
2033 		 * @return {tinymce.dom.DomQuery} Current set with the appended nodes.
2034 		 */
2035 		appendTo: function(val) {
2036 			DomQuery(val).append(this);
2037 
2038 			return this;
2039 		},
2040 
2041 		/**
2042 		 * Prepends the specified set nodes to the specified selector/instance.
2043 		 *
2044 		 * @method prependTo
2045 		 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to.
2046 		 * @return {tinymce.dom.DomQuery} Current set with the prepended nodes.
2047 		 */
2048 		prependTo: function(val) {
2049 			DomQuery(val).prepend(this);
2050 
2051 			return this;
2052 		},
2053 
2054 		/**
2055 		 * Replaces the nodes in set with the specified content.
2056 		 *
2057 		 * @method replaceWith
2058 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with.
2059 		 * @return {tinymce.dom.DomQuery} Set with replaced nodes.
2060 		 */
2061 		replaceWith: function(content) {
2062 			return this.before(content).remove();
2063 		},
2064 
2065 		/**
2066 		 * Wraps all elements in set with the specified wrapper.
2067 		 *
2068 		 * @method wrap
2069 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
2070 		 * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
2071 		 */
2072 		wrap: function(wrapper) {
2073 			return wrap(this, wrapper);
2074 		},
2075 
2076 		/**
2077 		 * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them
2078 		 * will be wrapped in the same wrapper.
2079 		 *
2080 		 * @method wrapAll
2081 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
2082 		 * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
2083 		 */
2084 		wrapAll: function(wrapper) {
2085 			return wrap(this, wrapper, true);
2086 		},
2087 
2088 		/**
2089 		 * Wraps all elements inner contents in set with the specified wrapper.
2090 		 *
2091 		 * @method wrapInner
2092 		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
2093 		 * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
2094 		 */
2095 		wrapInner: function(wrapper) {
2096 			this.each(function() {
2097 				DomQuery(this).contents().wrapAll(wrapper);
2098 			});
2099 
2100 			return this;
2101 		},
2102 
2103 		/**
2104 		 * Unwraps all elements by removing the parent element of each item in set.
2105 		 *
2106 		 * @method unwrap
2107 		 * @return {tinymce.dom.DomQuery} Set with unwrapped nodes.
2108 		 */
2109 		unwrap: function() {
2110 			return this.parent().each(function() {
2111 				DomQuery(this).replaceWith(this.childNodes);
2112 			});
2113 		},
2114 
2115 		/**
2116 		 * Clones all nodes in set.
2117 		 *
2118 		 * @method clone
2119 		 * @return {tinymce.dom.DomQuery} Set with cloned nodes.
2120 		 */
2121 		clone: function() {
2122 			var result = [];
2123 
2124 			this.each(function() {
2125 				result.push(this.cloneNode(true));
2126 			});
2127 
2128 			return DomQuery(result);
2129 		},
2130 
2131 		/**
2132 		 * Adds the specified class name to the current set elements.
2133 		 *
2134 		 * @method addClass
2135 		 * @param {String} className Class name to add.
2136 		 * @return {tinymce.dom.DomQuery} Current set.
2137 		 */
2138 		addClass: function(className) {
2139 			return this.toggleClass(className, true);
2140 		},
2141 
2142 		/**
2143 		 * Removes the specified class name to the current set elements.
2144 		 *
2145 		 * @method removeClass
2146 		 * @param {String} className Class name to remove.
2147 		 * @return {tinymce.dom.DomQuery} Current set.
2148 		 */
2149 		removeClass: function(className) {
2150 			return this.toggleClass(className, false);
2151 		},
2152 
2153 		/**
2154 		 * Toggles the specified class name on the current set elements.
2155 		 *
2156 		 * @method toggleClass
2157 		 * @param {String} className Class name to add/remove.
2158 		 * @param {Boolean} state Optional state to toggle on/off.
2159 		 * @return {tinymce.dom.DomQuery} Current set.
2160 		 */
2161 		toggleClass: function(className, state) {
2162 			var self = this;
2163 
2164 			// Functions are not supported
2165 			if (typeof className != 'string') {
2166 				return self;
2167 			}
2168 
2169 			if (className.indexOf(' ') !== -1) {
2170 				each(className.split(' '), function() {
2171 					self.toggleClass(this, state);
2172 				});
2173 			} else {
2174 				self.each(function(index, node) {
2175 					var existingClassName, classState;
2176 
2177 					classState = hasClass(node, className);
2178 					if (classState !== state) {
2179 						existingClassName = node.className;
2180 
2181 						if (classState) {
2182 							node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
2183 						} else {
2184 							node.className += existingClassName ? ' ' + className : className;
2185 						}
2186 					}
2187 				});
2188 			}
2189 
2190 			return self;
2191 		},
2192 
2193 		/**
2194 		 * Returns true/false if the first item in set has the specified class.
2195 		 *
2196 		 * @method hasClass
2197 		 * @param {String} className Class name to check for.
2198 		 * @return {Boolean} True/false if the set has the specified class.
2199 		 */
2200 		hasClass: function(className) {
2201 			return hasClass(this[0], className);
2202 		},
2203 
2204 		/**
2205 		 * Executes the callback function for each item DomQuery collection. If you return false in the
2206 		 * callback it will break the loop.
2207 		 *
2208 		 * @method each
2209 		 * @param {function} callback Callback function to execute for each item.
2210 		 * @return {tinymce.dom.DomQuery} Current set.
2211 		 */
2212 		each: function(callback) {
2213 			return each(this, callback);
2214 		},
2215 
2216 		/**
2217 		 * Binds an event with callback function to the elements in set.
2218 		 *
2219 		 * @method on
2220 		 * @param {String} name Name of the event to bind.
2221 		 * @param {function} callback Callback function to execute when the event occurs.
2222 		 * @return {tinymce.dom.DomQuery} Current set.
2223 		 */
2224 		on: function(name, callback) {
2225 			return this.each(function() {
2226 				Event.bind(this, name, callback);
2227 			});
2228 		},
2229 
2230 		/**
2231 		 * Unbinds an event with callback function to the elements in set.
2232 		 *
2233 		 * @method off
2234 		 * @param {String} name Optional name of the event to bind.
2235 		 * @param {function} callback Optional callback function to execute when the event occurs.
2236 		 * @return {tinymce.dom.DomQuery} Current set.
2237 		 */
2238 		off: function(name, callback) {
2239 			return this.each(function() {
2240 				Event.unbind(this, name, callback);
2241 			});
2242 		},
2243 
2244 		/**
2245 		 * Triggers the specified event by name or event object.
2246 		 *
2247 		 * @method trigger
2248 		 * @param {String/Object} name Name of the event to trigger or event object.
2249 		 * @return {tinymce.dom.DomQuery} Current set.
2250 		 */
2251 		trigger: function(name) {
2252 			return this.each(function() {
2253 				if (typeof name == 'object') {
2254 					Event.fire(this, name.type, name);
2255 				} else {
2256 					Event.fire(this, name);
2257 				}
2258 			});
2259 		},
2260 
2261 		/**
2262 		 * Shows all elements in set.
2263 		 *
2264 		 * @method show
2265 		 * @return {tinymce.dom.DomQuery} Current set.
2266 		 */
2267 		show: function() {
2268 			return this.css('display', '');
2269 		},
2270 
2271 		/**
2272 		 * Hides all elements in set.
2273 		 *
2274 		 * @method hide
2275 		 * @return {tinymce.dom.DomQuery} Current set.
2276 		 */
2277 		hide: function() {
2278 			return this.css('display', 'none');
2279 		},
2280 
2281 		/**
2282 		 * Slices the current set.
2283 		 *
2284 		 * @method slice
2285 		 * @param {Number} start Start index to slice at.
2286 		 * @param {Number} end Optional ened index to end slice at.
2287 		 * @return {tinymce.dom.DomQuery} Sliced set.
2288 		 */
2289 		slice: function() {
2290 			return new DomQuery(slice.apply(this, arguments));
2291 		},
2292 
2293 		/**
2294 		 * Makes the set equal to the specified index.
2295 		 *
2296 		 * @method eq
2297 		 * @param {Number} index Index to set it equal to.
2298 		 * @return {tinymce.dom.DomQuery} Single item set.
2299 		 */
2300 		eq: function(index) {
2301 			return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
2302 		},
2303 
2304 		/**
2305 		 * Makes the set equal to first element in set.
2306 		 *
2307 		 * @method first
2308 		 * @return {tinymce.dom.DomQuery} Single item set.
2309 		 */
2310 		first: function() {
2311 			return this.eq(0);
2312 		},
2313 
2314 		/**
2315 		 * Makes the set equal to last element in set.
2316 		 *
2317 		 * @method last
2318 		 * @return {tinymce.dom.DomQuery} Single item set.
2319 		 */
2320 		last: function() {
2321 			return this.eq(-1);
2322 		},
2323 
2324 		/**
2325 		 * Finds elements by the specified selector for each element in set.
2326 		 *
2327 		 * @method find
2328 		 * @param {String} selector Selector to find elements by.
2329 		 * @return {tinymce.dom.DomQuery} Set with matches elements.
2330 		 */
2331 		find: function(selector) {
2332 			var i, l, ret = [];
2333 
2334 			for (i = 0, l = this.length; i < l; i++) {
2335 				DomQuery.find(selector, this[i], ret);
2336 			}
2337 
2338 			return DomQuery(ret);
2339 		},
2340 
2341 		/**
2342 		 * Filters the current set with the specified selector.
2343 		 *
2344 		 * @method filter
2345 		 * @param {String/function} selector Selector to filter elements by.
2346 		 * @return {tinymce.dom.DomQuery} Set with filtered elements.
2347 		 */
2348 		filter: function(selector) {
2349 			if (typeof selector == 'function') {
2350 				return DomQuery(grep(this.toArray(), function(item, i) {
2351 					return selector(i, item);
2352 				}));
2353 			}
2354 
2355 			return DomQuery(DomQuery.filter(selector, this.toArray()));
2356 		},
2357 
2358 		/**
2359 		 * Gets the current node or any partent matching the specified selector.
2360 		 *
2361 		 * @method closest
2362 		 * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find.
2363 		 * @return {tinymce.dom.DomQuery} Set with closest elements.
2364 		 */
2365 		closest: function(selector) {
2366 			var result = [];
2367 
2368 			if (selector instanceof DomQuery) {
2369 				selector = selector[0];
2370 			}
2371 
2372 			this.each(function(i, node) {
2373 				while (node) {
2374 					if (typeof selector == 'string' && DomQuery(node).is(selector)) {
2375 						result.push(node);
2376 						break;
2377 					} else if (node == selector) {
2378 						result.push(node);
2379 						break;
2380 					}
2381 
2382 					node = node.parentNode;
2383 				}
2384 			});
2385 
2386 			return DomQuery(result);
2387 		},
2388 
2389 		/**
2390 		 * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
2391 		 *
2392 		 * @method offset
2393 		 * @param {Object} offset Optional offset object to set on each item.
2394 		 * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
2395 		 */
2396 		offset: function(offset) {
2397 			var elm, doc, docElm;
2398 			var x = 0, y = 0, pos;
2399 
2400 			if (!offset) {
2401 				elm = this[0];
2402 
2403 				if (elm) {
2404 					doc = elm.ownerDocument;
2405 					docElm = doc.documentElement;
2406 
2407 					if (elm.getBoundingClientRect) {
2408 						pos = elm.getBoundingClientRect();
2409 						x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
2410 						y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
2411 					}
2412 				}
2413 
2414 				return {
2415 					left: x,
2416 					top: y
2417 				};
2418 			}
2419 
2420 			return this.css(offset);
2421 		},
2422 
2423 		push: push,
2424 		sort: [].sort,
2425 		splice: [].splice
2426 	};
2427 
2428 	// Static members
2429 	Tools.extend(DomQuery, {
2430 		/**
2431 		 * Extends the specified object with one or more objects.
2432 		 *
2433 		 * @static
2434 		 * @method extend
2435 		 * @param {Object} target Target object to extend with new items.
2436 		 * @param {Object..} object Object to extend the target with.
2437 		 * @return {Object} Extended input object.
2438 		 */
2439 		extend: Tools.extend,
2440 
2441 		/**
2442 		 * Creates an array out of an array like object.
2443 		 *
2444 		 * @static
2445 		 * @method makeArray
2446 		 * @param {Object} object Object to convert to array.
2447 		 * @return {Arrau} Array produced from object.
2448 		 */
2449 		makeArray: Tools.toArray,
2450 
2451 		/**
2452 		 * Returns the index of the specified item inside the array.
2453 		 *
2454 		 * @static
2455 		 * @method inArray
2456 		 * @param {Object} item Item to look for.
2457 		 * @param {Array} array Array to look for item in.
2458 		 * @return {Number} Index of the item or -1.
2459 		 */
2460 		inArray: inArray,
2461 
2462 		/**
2463 		 * Returns true/false if the specified object is an array or not.
2464 		 *
2465 		 * @static
2466 		 * @method isArray
2467 		 * @param {Object} array Object to check if it's an array or not.
2468 		 * @return {Boolean} True/false if the object is an array.
2469 		 */
2470 		isArray: Tools.isArray,
2471 
2472 		/**
2473 		 * Executes the callback function for each item in array/object. If you return false in the
2474 		 * callback it will break the loop.
2475 		 *
2476 		 * @static
2477 		 * @method each
2478 		 * @param {Object} obj Object to iterate.
2479 		 * @param {function} callback Callback function to execute for each item.
2480 		 */
2481 		each: each,
2482 
2483 		/**
2484 		 * Removes whitespace from the beginning and end of a string.
2485 		 *
2486 		 * @static
2487 		 * @method trim
2488 		 * @param {String} str String to remove whitespace from.
2489 		 * @return {String} New string with removed whitespace.
2490 		 */
2491 		trim: trim,
2492 
2493 		/**
2494 		 * Filters out items from the input array by calling the specified function for each item.
2495 		 * If the function returns false the item will be excluded if it returns true it will be included.
2496 		 *
2497 		 * @static
2498 		 * @method grep
2499 		 * @param {Array} array Array of items to loop though.
2500 		 * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
2501 		 * @return {Array} New array with values imported and filtered based in input.
2502 		 * @example
2503 		 * // Filter out some items, this will return an array with 4 and 5
2504 		 * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
2505 		 */
2506 		grep: grep,
2507 
2508 		// Sizzle
2509 		find: Sizzle,
2510 		expr: Sizzle.selectors,
2511 		unique: Sizzle.uniqueSort,
2512 		text: Sizzle.getText,
2513 		contains: Sizzle.contains,
2514 		filter: function(expr, elems, not) {
2515 			var i = elems.length;
2516 
2517 			if (not) {
2518 				expr = ":not(" + expr + ")";
2519 			}
2520 
2521 			while (i--) {
2522 				if (elems[i].nodeType != 1) {
2523 					elems.splice(i, 1);
2524 				}
2525 			}
2526 
2527 			if (elems.length === 1) {
2528 				elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
2529 			} else {
2530 				elems = DomQuery.find.matches(expr, elems);
2531 			}
2532 
2533 			return elems;
2534 		}
2535 	});
2536 
2537 	function dir(el, prop, until) {
2538 		var matched = [], cur = el[prop];
2539 
2540 		if (typeof until != 'string' && until instanceof DomQuery) {
2541 			until = until[0];
2542 		}
2543 
2544 		while (cur && cur.nodeType !== 9) {
2545 			if (until !== undefined) {
2546 				if (cur === until) {
2547 					break;
2548 				}
2549 
2550 				if (typeof until == 'string' && DomQuery(cur).is(until)) {
2551 					break;
2552 				}
2553 			}
2554 
2555 			if (cur.nodeType === 1) {
2556 				matched.push(cur);
2557 			}
2558 
2559 			cur = cur[prop];
2560 		}
2561 
2562 		return matched;
2563 	}
2564 
2565 	function sibling(node, siblingName, nodeType, until) {
2566 		var result = [];
2567 
2568 		if (until instanceof DomQuery) {
2569 			until = until[0];
2570 		}
2571 
2572 		for (; node; node = node[siblingName]) {
2573 			if (nodeType && node.nodeType !== nodeType) {
2574 				continue;
2575 			}
2576 
2577 			if (until !== undefined) {
2578 				if (node === until) {
2579 					break;
2580 				}
2581 
2582 				if (typeof until == 'string' && DomQuery(node).is(until)) {
2583 					break;
2584 				}
2585 			}
2586 
2587 			result.push(node);
2588 		}
2589 
2590 		return result;
2591 	}
2592 
2593 	function firstSibling(node, siblingName, nodeType) {
2594 		for (node = node[siblingName]; node; node = node[siblingName]) {
2595 			if (node.nodeType == nodeType) {
2596 				return node;
2597 			}
2598 		}
2599 
2600 		return null;
2601 	}
2602 
2603 	each({
2604 		/**
2605 		 * Returns a new collection with the parent of each item in current collection matching the optional selector.
2606 		 *
2607 		 * @method parent
2608 		 * @param {String} selector Selector to match parents agains.
2609 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2610 		 */
2611 		parent: function(node) {
2612 			var parent = node.parentNode;
2613 
2614 			return parent && parent.nodeType !== 11 ? parent : null;
2615 		},
2616 
2617 		/**
2618 		 * Returns a new collection with the all the parents of each item in current collection matching the optional selector.
2619 		 *
2620 		 * @method parents
2621 		 * @param {String} selector Selector to match parents agains.
2622 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2623 		 */
2624 		parents: function(node) {
2625 			return dir(node, "parentNode");
2626 		},
2627 
2628 		/**
2629 		 * Returns a new collection with next sibling of each item in current collection matching the optional selector.
2630 		 *
2631 		 * @method next
2632 		 * @param {String} selector Selector to match the next element against.
2633 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2634 		 */
2635 		next: function(node) {
2636 			return firstSibling(node, 'nextSibling', 1);
2637 		},
2638 
2639 		/**
2640 		 * Returns a new collection with previous sibling of each item in current collection matching the optional selector.
2641 		 *
2642 		 * @method prev
2643 		 * @param {String} selector Selector to match the previous element against.
2644 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2645 		 */
2646 		prev: function(node) {
2647 			return firstSibling(node, 'previousSibling', 1);
2648 		},
2649 
2650 		/**
2651 		 * Returns all child elements matching the optional selector.
2652 		 *
2653 		 * @method children
2654 		 * @param {String} selector Selector to match the elements against.
2655 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2656 		 */
2657 		children: function(node) {
2658 			return sibling(node.firstChild, 'nextSibling', 1);
2659 		},
2660 
2661 		/**
2662 		 * Returns all child nodes matching the optional selector.
2663 		 *
2664 		 * @method contents
2665 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2666 		 */
2667 		contents: function(node) {
2668 			return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
2669 		}
2670 	}, function(name, fn) {
2671 		DomQuery.fn[name] = function(selector) {
2672 			var self = this, result = [];
2673 
2674 			self.each(function() {
2675 				var nodes = fn.call(result, this, selector, result);
2676 
2677 				if (nodes) {
2678 					if (DomQuery.isArray(nodes)) {
2679 						result.push.apply(result, nodes);
2680 					} else {
2681 						result.push(nodes);
2682 					}
2683 				}
2684 			});
2685 
2686 			// If traversing on multiple elements we might get the same elements twice
2687 			if (this.length > 1) {
2688 				result = DomQuery.unique(result);
2689 
2690 				if (name.indexOf('parents') === 0) {
2691 					result = result.reverse();
2692 				}
2693 			}
2694 
2695 			result = DomQuery(result);
2696 
2697 			if (selector) {
2698 				return result.filter(selector);
2699 			}
2700 
2701 			return result;
2702 		};
2703 	});
2704 
2705 	each({
2706 		/**
2707 		 * Returns a new collection with the all the parents until the matching selector/element
2708 		 * of each item in current collection matching the optional selector.
2709 		 *
2710 		 * @method parentsUntil
2711 		 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2712 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2713 		 */
2714 		parentsUntil: function(node, until) {
2715 			return dir(node, "parentNode", until);
2716 		},
2717 
2718 		/**
2719 		 * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
2720 		 *
2721 		 * @method nextUntil
2722 		 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2723 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2724 		 */
2725 		nextUntil: function(node, until) {
2726 			return sibling(node, 'nextSibling', 1, until).slice(1);
2727 		},
2728 
2729 		/**
2730 		 * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
2731 		 *
2732 		 * @method prevUntil
2733 		 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2734 		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2735 		 */
2736 		prevUntil: function(node, until) {
2737 			return sibling(node, 'previousSibling', 1, until).slice(1);
2738 		}
2739 	}, function(name, fn) {
2740 		DomQuery.fn[name] = function(selector, filter) {
2741 			var self = this, result = [];
2742 
2743 			self.each(function() {
2744 				var nodes = fn.call(result, this, selector, result);
2745 
2746 				if (nodes) {
2747 					if (DomQuery.isArray(nodes)) {
2748 						result.push.apply(result, nodes);
2749 					} else {
2750 						result.push(nodes);
2751 					}
2752 				}
2753 			});
2754 
2755 			// If traversing on multiple elements we might get the same elements twice
2756 			if (this.length > 1) {
2757 				result = DomQuery.unique(result);
2758 
2759 				if (name.indexOf('parents') === 0 || name === 'prevUntil') {
2760 					result = result.reverse();
2761 				}
2762 			}
2763 
2764 			result = DomQuery(result);
2765 
2766 			if (filter) {
2767 				return result.filter(filter);
2768 			}
2769 
2770 			return result;
2771 		};
2772 	});
2773 
2774 	/**
2775 	 * Returns true/false if the current set items matches the selector.
2776 	 *
2777 	 * @method is
2778 	 * @param {String} selector Selector to match the elements against.
2779 	 * @return {Boolean} True/false if the current set matches the selector.
2780 	 */
2781 	DomQuery.fn.is = function(selector) {
2782 		return !!selector && this.filter(selector).length > 0;
2783 	};
2784 
2785 	DomQuery.fn.init.prototype = DomQuery.fn;
2786 
2787 	DomQuery.overrideDefaults = function(callback) {
2788 		var defaults;
2789 
2790 		function sub(selector, context) {
2791 			defaults = defaults || callback();
2792 
2793 			if (arguments.length === 0) {
2794 				selector = defaults.element;
2795 			}
2796 
2797 			if (!context) {
2798 				context = defaults.context;
2799 			}
2800 
2801 			return new sub.fn.init(selector, context);
2802 		}
2803 
2804 		DomQuery.extend(sub, this);
2805 
2806 		return sub;
2807 	};
2808 
2809 	function appendHooks(targetHooks, prop, hooks) {
2810 		each(hooks, function(name, func) {
2811 			targetHooks[name] = targetHooks[name] || {};
2812 			targetHooks[name][prop] = func;
2813 		});
2814 	}
2815 
2816 	if (Env.ie && Env.ie < 8) {
2817 		appendHooks(attrHooks, 'get', {
2818 			maxlength: function(elm) {
2819 				var value = elm.maxLength;
2820 
2821 				if (value === 0x7fffffff) {
2822 					return undef;
2823 				}
2824 
2825 				return value;
2826 			},
2827 
2828 			size: function(elm) {
2829 				var value = elm.size;
2830 
2831 				if (value === 20) {
2832 					return undef;
2833 				}
2834 
2835 				return value;
2836 			},
2837 
2838 			'class': function(elm) {
2839 				return elm.className;
2840 			},
2841 
2842 			style: function(elm) {
2843 				var value = elm.style.cssText;
2844 
2845 				if (value.length === 0) {
2846 					return undef;
2847 				}
2848 
2849 				return value;
2850 			}
2851 		});
2852 
2853 		appendHooks(attrHooks, 'set', {
2854 			'class': function(elm, value) {
2855 				elm.className = value;
2856 			},
2857 
2858 			style: function(elm, value) {
2859 				elm.style.cssText = value;
2860 			}
2861 		});
2862 	}
2863 
2864 	if (Env.ie && Env.ie < 9) {
2865 		/*jshint sub:true */
2866 		/*eslint dot-notation: 0*/
2867 		cssFix['float'] = 'styleFloat';
2868 
2869 		appendHooks(cssHooks, 'set', {
2870 			opacity: function(elm, value) {
2871 				var style = elm.style;
2872 
2873 				if (value === null || value === '') {
2874 					style.removeAttribute('filter');
2875 				} else {
2876 					style.zoom = 1;
2877 					style.filter = 'alpha(opacity=' + (value * 100) + ')';
2878 				}
2879 			}
2880 		});
2881 	}
2882 
2883 	DomQuery.attrHooks = attrHooks;
2884 	DomQuery.cssHooks = cssHooks;
2885 
2886 	return DomQuery;
2887 });
2888 
2889 // Included from: js/tinymce/classes/html/Styles.js
2890 
2891 /**
2892  * Styles.js
2893  *
2894  * Copyright, Moxiecode Systems AB
2895  * Released under LGPL License.
2896  *
2897  * License: http://www.tinymce.com/license
2898  * Contributing: http://www.tinymce.com/contributing
2899  */
2900 
2901 /**
2902  * This class is used to parse CSS styles it also compresses styles to reduce the output size.
2903  *
2904  * @example
2905  * var Styles = new tinymce.html.Styles({
2906  *    url_converter: function(url) {
2907  *       return url;
2908  *    }
2909  * });
2910  *
2911  * styles = Styles.parse('border: 1px solid red');
2912  * styles.color = 'red';
2913  *
2914  * console.log(new tinymce.html.StyleSerializer().serialize(styles));
2915  *
2916  * @class tinymce.html.Styles
2917  * @version 3.4
2918  */
2919 define("tinymce/html/Styles", [], function() {
2920 	return function(settings, schema) {
2921 		/*jshint maxlen:255 */
2922 		/*eslint max-len:0 */
2923 		var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2924 			urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2925 			styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2926 			trimRightRegExp = /\s+$/,
2927 			undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
2928 
2929 		settings = settings || {};
2930 
2931 		if (schema) {
2932 			validStyles = schema.getValidStyles();
2933 			invalidStyles = schema.getInvalidStyles();
2934 		}
2935 
2936 		encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
2937 		for (i = 0; i < encodingItems.length; i++) {
2938 			encodingLookup[encodingItems[i]] = invisibleChar + i;
2939 			encodingLookup[invisibleChar + i] = encodingItems[i];
2940 		}
2941 
2942 		function toHex(match, r, g, b) {
2943 			function hex(val) {
2944 				val = parseInt(val, 10).toString(16);
2945 
2946 				return val.length > 1 ? val : '0' + val; // 0 -> 00
2947 			}
2948 
2949 			return '#' + hex(r) + hex(g) + hex(b);
2950 		}
2951 
2952 		return {
2953 			/**
2954 			 * Parses the specified RGB color value and returns a hex version of that color.
2955 			 *
2956 			 * @method toHex
2957 			 * @param {String} color RGB string value like rgb(1,2,3)
2958 			 * @return {String} Hex version of that RGB value like #FF00FF.
2959 			 */
2960 			toHex: function(color) {
2961 				return color.replace(rgbRegExp, toHex);
2962 			},
2963 
2964 			/**
2965 			 * Parses the specified style value into an object collection. This parser will also
2966 			 * merge and remove any redundant items that browsers might have added. It will also convert non hex
2967 			 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
2968 			 *
2969 			 * @method parse
2970 			 * @param {String} css Style value to parse for example: border:1px solid red;.
2971 			 * @return {Object} Object representation of that style like {border: '1px solid red'}
2972 			 */
2973 			parse: function(css) {
2974 				var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
2975 				var urlConverterScope = settings.url_converter_scope || this;
2976 
2977 				function compress(prefix, suffix, noJoin) {
2978 					var top, right, bottom, left;
2979 
2980 					top = styles[prefix + '-top' + suffix];
2981 					if (!top) {
2982 						return;
2983 					}
2984 
2985 					right = styles[prefix + '-right' + suffix];
2986 					if (!right) {
2987 						return;
2988 					}
2989 
2990 					bottom = styles[prefix + '-bottom' + suffix];
2991 					if (!bottom) {
2992 						return;
2993 					}
2994 
2995 					left = styles[prefix + '-left' + suffix];
2996 					if (!left) {
2997 						return;
2998 					}
2999 
3000 					var box = [top, right, bottom, left];
3001 					i = box.length - 1;
3002 					while (i--) {
3003 						if (box[i] !== box[i + 1]) {
3004 							break;
3005 						}
3006 					}
3007 
3008 					if (i > -1 && noJoin) {
3009 						return;
3010 					}
3011 
3012 					styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
3013 					delete styles[prefix + '-top' + suffix];
3014 					delete styles[prefix + '-right' + suffix];
3015 					delete styles[prefix + '-bottom' + suffix];
3016 					delete styles[prefix + '-left' + suffix];
3017 				}
3018 
3019 				/**
3020 				 * Checks if the specific style can be compressed in other words if all border-width are equal.
3021 				 */
3022 				function canCompress(key) {
3023 					var value = styles[key], i;
3024 
3025 					if (!value) {
3026 						return;
3027 					}
3028 
3029 					value = value.split(' ');
3030 					i = value.length;
3031 					while (i--) {
3032 						if (value[i] !== value[0]) {
3033 							return false;
3034 						}
3035 					}
3036 
3037 					styles[key] = value[0];
3038 
3039 					return true;
3040 				}
3041 
3042 				/**
3043 				 * Compresses multiple styles into one style.
3044 				 */
3045 				function compress2(target, a, b, c) {
3046 					if (!canCompress(a)) {
3047 						return;
3048 					}
3049 
3050 					if (!canCompress(b)) {
3051 						return;
3052 					}
3053 
3054 					if (!canCompress(c)) {
3055 						return;
3056 					}
3057 
3058 					// Compress
3059 					styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
3060 					delete styles[a];
3061 					delete styles[b];
3062 					delete styles[c];
3063 				}
3064 
3065 				// Encodes the specified string by replacing all \" \' ; : with _<num>
3066 				function encode(str) {
3067 					isEncoded = true;
3068 
3069 					return encodingLookup[str];
3070 				}
3071 
3072 				// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
3073 				// It will also decode the \" \' if keep_slashes is set to fale or omitted
3074 				function decode(str, keep_slashes) {
3075 					if (isEncoded) {
3076 						str = str.replace(/\uFEFF[0-9]/g, function(str) {
3077 							return encodingLookup[str];
3078 						});
3079 					}
3080 
3081 					if (!keep_slashes) {
3082 						str = str.replace(/\\([\'\";:])/g, "$1");
3083 					}
3084 
3085 					return str;
3086 				}
3087 
3088 				function processUrl(match, url, url2, url3, str, str2) {
3089 					str = str || str2;
3090 
3091 					if (str) {
3092 						str = decode(str);
3093 
3094 						// Force strings into single quote format
3095 						return "'" + str.replace(/\'/g, "\\'") + "'";
3096 					}
3097 
3098 					url = decode(url || url2 || url3);
3099 
3100 					if (!settings.allow_script_urls) {
3101 						var scriptUrl = url.replace(/[\s\r\n]+/, '');
3102 
3103 						if (/(java|vb)script:/i.test(scriptUrl)) {
3104 							return "";
3105 						}
3106 
3107 						if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
3108 							return "";
3109 						}
3110 					}
3111 
3112 					// Convert the URL to relative/absolute depending on config
3113 					if (urlConverter) {
3114 						url = urlConverter.call(urlConverterScope, url, 'style');
3115 					}
3116 
3117 					// Output new URL format
3118 					return "url('" + url.replace(/\'/g, "\\'") + "')";
3119 				}
3120 
3121 				if (css) {
3122 					css = css.replace(/[\u0000-\u001F]/g, '');
3123 
3124 					// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
3125 					css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
3126 						return str.replace(/[;:]/g, encode);
3127 					});
3128 
3129 					// Parse styles
3130 					while ((matches = styleRegExp.exec(css))) {
3131 						name = matches[1].replace(trimRightRegExp, '').toLowerCase();
3132 						value = matches[2].replace(trimRightRegExp, '');
3133 
3134 						// Decode escaped sequences like \65 -> e
3135 						/*jshint loopfunc:true*/
3136 						/*eslint no-loop-func:0 */
3137 						value = value.replace(/\\[0-9a-f]+/g, function(e) {
3138 							return String.fromCharCode(parseInt(e.substr(1), 16));
3139 						});
3140 
3141 						if (name && value.length > 0) {
3142 							// Don't allow behavior name or expression/comments within the values
3143 							if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
3144 								continue;
3145 							}
3146 
3147 							// Opera will produce 700 instead of bold in their style values
3148 							if (name === 'font-weight' && value === '700') {
3149 								value = 'bold';
3150 							} else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
3151 								value = value.toLowerCase();
3152 							}
3153 
3154 							// Convert RGB colors to HEX
3155 							value = value.replace(rgbRegExp, toHex);
3156 
3157 							// Convert URLs and force them into url('value') format
3158 							value = value.replace(urlOrStrRegExp, processUrl);
3159 							styles[name] = isEncoded ? decode(value, true) : value;
3160 						}
3161 
3162 						styleRegExp.lastIndex = matches.index + matches[0].length;
3163 					}
3164 					// Compress the styles to reduce it's size for example IE will expand styles
3165 					compress("border", "", true);
3166 					compress("border", "-width");
3167 					compress("border", "-color");
3168 					compress("border", "-style");
3169 					compress("padding", "");
3170 					compress("margin", "");
3171 					compress2('border', 'border-width', 'border-style', 'border-color');
3172 
3173 					// Remove pointless border, IE produces these
3174 					if (styles.border === 'medium none') {
3175 						delete styles.border;
3176 					}
3177 
3178 					// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
3179 					// So lets asume it shouldn't be there
3180 					if (styles['border-image'] === 'none') {
3181 						delete styles['border-image'];
3182 					}
3183 				}
3184 
3185 				return styles;
3186 			},
3187 
3188 			/**
3189 			 * Serializes the specified style object into a string.
3190 			 *
3191 			 * @method serialize
3192 			 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
3193 			 * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
3194 			 * @return {String} String representation of the style object for example: border: 1px solid red.
3195 			 */
3196 			serialize: function(styles, elementName) {
3197 				var css = '', name, value;
3198 
3199 				function serializeStyles(name) {
3200 					var styleList, i, l, value;
3201 
3202 					styleList = validStyles[name];
3203 					if (styleList) {
3204 						for (i = 0, l = styleList.length; i < l; i++) {
3205 							name = styleList[i];
3206 							value = styles[name];
3207 
3208 							if (value !== undef && value.length > 0) {
3209 								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
3210 							}
3211 						}
3212 					}
3213 				}
3214 
3215 				function isValid(name, elementName) {
3216 					var styleMap;
3217 
3218 					styleMap = invalidStyles['*'];
3219 					if (styleMap && styleMap[name]) {
3220 						return false;
3221 					}
3222 
3223 					styleMap = invalidStyles[elementName];
3224 					if (styleMap && styleMap[name]) {
3225 						return false;
3226 					}
3227 
3228 					return true;
3229 				}
3230 
3231 				// Serialize styles according to schema
3232 				if (elementName && validStyles) {
3233 					// Serialize global styles and element specific styles
3234 					serializeStyles('*');
3235 					serializeStyles(elementName);
3236 				} else {
3237 					// Output the styles in the order they are inside the object
3238 					for (name in styles) {
3239 						value = styles[name];
3240 
3241 						if (value !== undef && value.length > 0) {
3242 							if (!invalidStyles || isValid(name, elementName)) {
3243 								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
3244 							}
3245 						}
3246 					}
3247 				}
3248 
3249 				return css;
3250 			}
3251 		};
3252 	};
3253 });
3254 
3255 // Included from: js/tinymce/classes/dom/TreeWalker.js
3256 
3257 /**
3258  * TreeWalker.js
3259  *
3260  * Copyright, Moxiecode Systems AB
3261  * Released under LGPL License.
3262  *
3263  * License: http://www.tinymce.com/license
3264  * Contributing: http://www.tinymce.com/contributing
3265  */
3266 
3267 /**
3268  * TreeWalker class enables you to walk the DOM in a linear manner.
3269  *
3270  * @class tinymce.dom.TreeWalker
3271  * @example
3272  * var walker = new tinymce.dom.TreeWalker(startNode);
3273  *
3274  * do {
3275  *     console.log(walker.current());
3276  * } while (walker.next());
3277  */
3278 define("tinymce/dom/TreeWalker", [], function() {
3279 	/**
3280 	 * Constructs a new TreeWalker instance.
3281 	 *
3282 	 * @constructor
3283 	 * @method TreeWalker
3284 	 * @param {Node} startNode Node to start walking from.
3285 	 * @param {node} rootNode Optional root node to never walk out of.
3286 	 */
3287 	return function(startNode, rootNode) {
3288 		var node = startNode;
3289 
3290 		function findSibling(node, startName, siblingName, shallow) {
3291 			var sibling, parent;
3292 
3293 			if (node) {
3294 				// Walk into nodes if it has a start
3295 				if (!shallow && node[startName]) {
3296 					return node[startName];
3297 				}
3298 
3299 				// Return the sibling if it has one
3300 				if (node != rootNode) {
3301 					sibling = node[siblingName];
3302 					if (sibling) {
3303 						return sibling;
3304 					}
3305 
3306 					// Walk up the parents to look for siblings
3307 					for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
3308 						sibling = parent[siblingName];
3309 						if (sibling) {
3310 							return sibling;
3311 						}
3312 					}
3313 				}
3314 			}
3315 		}
3316 
3317 		/**
3318 		 * Returns the current node.
3319 		 *
3320 		 * @method current
3321 		 * @return {Node} Current node where the walker is.
3322 		 */
3323 		this.current = function() {
3324 			return node;
3325 		};
3326 
3327 		/**
3328 		 * Walks to the next node in tree.
3329 		 *
3330 		 * @method next
3331 		 * @return {Node} Current node where the walker is after moving to the next node.
3332 		 */
3333 		this.next = function(shallow) {
3334 			node = findSibling(node, 'firstChild', 'nextSibling', shallow);
3335 			return node;
3336 		};
3337 
3338 		/**
3339 		 * Walks to the previous node in tree.
3340 		 *
3341 		 * @method prev
3342 		 * @return {Node} Current node where the walker is after moving to the previous node.
3343 		 */
3344 		this.prev = function(shallow) {
3345 			node = findSibling(node, 'lastChild', 'previousSibling', shallow);
3346 			return node;
3347 		};
3348 	};
3349 });
3350 
3351 // Included from: js/tinymce/classes/dom/Range.js
3352 
3353 /**
3354  * Range.js
3355  *
3356  * Copyright, Moxiecode Systems AB
3357  * Released under LGPL License.
3358  *
3359  * License: http://www.tinymce.com/license
3360  * Contributing: http://www.tinymce.com/contributing
3361  */
3362 
3363 define("tinymce/dom/Range", [
3364 	"tinymce/util/Tools"
3365 ], function(Tools) {
3366 	// Range constructor
3367 	function Range(dom) {
3368 		var self = this,
3369 			doc = dom.doc,
3370 			EXTRACT = 0,
3371 			CLONE = 1,
3372 			DELETE = 2,
3373 			TRUE = true,
3374 			FALSE = false,
3375 			START_OFFSET = 'startOffset',
3376 			START_CONTAINER = 'startContainer',
3377 			END_CONTAINER = 'endContainer',
3378 			END_OFFSET = 'endOffset',
3379 			extend = Tools.extend,
3380 			nodeIndex = dom.nodeIndex;
3381 
3382 		function createDocumentFragment() {
3383 			return doc.createDocumentFragment();
3384 		}
3385 
3386 		function setStart(n, o) {
3387 			_setEndPoint(TRUE, n, o);
3388 		}
3389 
3390 		function setEnd(n, o) {
3391 			_setEndPoint(FALSE, n, o);
3392 		}
3393 
3394 		function setStartBefore(n) {
3395 			setStart(n.parentNode, nodeIndex(n));
3396 		}
3397 
3398 		function setStartAfter(n) {
3399 			setStart(n.parentNode, nodeIndex(n) + 1);
3400 		}
3401 
3402 		function setEndBefore(n) {
3403 			setEnd(n.parentNode, nodeIndex(n));
3404 		}
3405 
3406 		function setEndAfter(n) {
3407 			setEnd(n.parentNode, nodeIndex(n) + 1);
3408 		}
3409 
3410 		function collapse(ts) {
3411 			if (ts) {
3412 				self[END_CONTAINER] = self[START_CONTAINER];
3413 				self[END_OFFSET] = self[START_OFFSET];
3414 			} else {
3415 				self[START_CONTAINER] = self[END_CONTAINER];
3416 				self[START_OFFSET] = self[END_OFFSET];
3417 			}
3418 
3419 			self.collapsed = TRUE;
3420 		}
3421 
3422 		function selectNode(n) {
3423 			setStartBefore(n);
3424 			setEndAfter(n);
3425 		}
3426 
3427 		function selectNodeContents(n) {
3428 			setStart(n, 0);
3429 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
3430 		}
3431 
3432 		function compareBoundaryPoints(h, r) {
3433 			var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
3434 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
3435 
3436 			// Check START_TO_START
3437 			if (h === 0) {
3438 				return _compareBoundaryPoints(sc, so, rsc, rso);
3439 			}
3440 
3441 			// Check START_TO_END
3442 			if (h === 1) {
3443 				return _compareBoundaryPoints(ec, eo, rsc, rso);
3444 			}
3445 
3446 			// Check END_TO_END
3447 			if (h === 2) {
3448 				return _compareBoundaryPoints(ec, eo, rec, reo);
3449 			}
3450 
3451 			// Check END_TO_START
3452 			if (h === 3) {
3453 				return _compareBoundaryPoints(sc, so, rec, reo);
3454 			}
3455 		}
3456 
3457 		function deleteContents() {
3458 			_traverse(DELETE);
3459 		}
3460 
3461 		function extractContents() {
3462 			return _traverse(EXTRACT);
3463 		}
3464 
3465 		function cloneContents() {
3466 			return _traverse(CLONE);
3467 		}
3468 
3469 		function insertNode(n) {
3470 			var startContainer = this[START_CONTAINER],
3471 				startOffset = this[START_OFFSET], nn, o;
3472 
3473 			// Node is TEXT_NODE or CDATA
3474 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
3475 				if (!startOffset) {
3476 					// At the start of text
3477 					startContainer.parentNode.insertBefore(n, startContainer);
3478 				} else if (startOffset >= startContainer.nodeValue.length) {
3479 					// At the end of text
3480 					dom.insertAfter(n, startContainer);
3481 				} else {
3482 					// Middle, need to split
3483 					nn = startContainer.splitText(startOffset);
3484 					startContainer.parentNode.insertBefore(n, nn);
3485 				}
3486 			} else {
3487 				// Insert element node
3488 				if (startContainer.childNodes.length > 0) {
3489 					o = startContainer.childNodes[startOffset];
3490 				}
3491 
3492 				if (o) {
3493 					startContainer.insertBefore(n, o);
3494 				} else {
3495 					if (startContainer.nodeType == 3) {
3496 						dom.insertAfter(n, startContainer);
3497 					} else {
3498 						startContainer.appendChild(n);
3499 					}
3500 				}
3501 			}
3502 		}
3503 
3504 		function surroundContents(n) {
3505 			var f = self.extractContents();
3506 
3507 			self.insertNode(n);
3508 			n.appendChild(f);
3509 			self.selectNode(n);
3510 		}
3511 
3512 		function cloneRange() {
3513 			return extend(new Range(dom), {
3514 				startContainer: self[START_CONTAINER],
3515 				startOffset: self[START_OFFSET],
3516 				endContainer: self[END_CONTAINER],
3517 				endOffset: self[END_OFFSET],
3518 				collapsed: self.collapsed,
3519 				commonAncestorContainer: self.commonAncestorContainer
3520 			});
3521 		}
3522 
3523 		// Private methods
3524 
3525 		function _getSelectedNode(container, offset) {
3526 			var child;
3527 
3528 			if (container.nodeType == 3 /* TEXT_NODE */) {
3529 				return container;
3530 			}
3531 
3532 			if (offset < 0) {
3533 				return container;
3534 			}
3535 
3536 			child = container.firstChild;
3537 			while (child && offset > 0) {
3538 				--offset;
3539 				child = child.nextSibling;
3540 			}
3541 
3542 			if (child) {
3543 				return child;
3544 			}
3545 
3546 			return container;
3547 		}
3548 
3549 		function _isCollapsed() {
3550 			return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
3551 		}
3552 
3553 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
3554 			var c, offsetC, n, cmnRoot, childA, childB;
3555 
3556 			// In the first case the boundary-points have the same container. A is before B
3557 			// if its offset is less than the offset of B, A is equal to B if its offset is
3558 			// equal to the offset of B, and A is after B if its offset is greater than the
3559 			// offset of B.
3560 			if (containerA == containerB) {
3561 				if (offsetA == offsetB) {
3562 					return 0; // equal
3563 				}
3564 
3565 				if (offsetA < offsetB) {
3566 					return -1; // before
3567 				}
3568 
3569 				return 1; // after
3570 			}
3571 
3572 			// In the second case a child node C of the container of A is an ancestor
3573 			// container of B. In this case, A is before B if the offset of A is less than or
3574 			// equal to the index of the child node C and A is after B otherwise.
3575 			c = containerB;
3576 			while (c && c.parentNode != containerA) {
3577 				c = c.parentNode;
3578 			}
3579 
3580 			if (c) {
3581 				offsetC = 0;
3582 				n = containerA.firstChild;
3583 
3584 				while (n != c && offsetC < offsetA) {
3585 					offsetC++;
3586 					n = n.nextSibling;
3587 				}
3588 
3589 				if (offsetA <= offsetC) {
3590 					return -1; // before
3591 				}
3592 
3593 				return 1; // after
3594 			}
3595 
3596 			// In the third case a child node C of the container of B is an ancestor container
3597 			// of A. In this case, A is before B if the index of the child node C is less than
3598 			// the offset of B and A is after B otherwise.
3599 			c = containerA;
3600 			while (c && c.parentNode != containerB) {
3601 				c = c.parentNode;
3602 			}
3603 
3604 			if (c) {
3605 				offsetC = 0;
3606 				n = containerB.firstChild;
3607 
3608 				while (n != c && offsetC < offsetB) {
3609 					offsetC++;
3610 					n = n.nextSibling;
3611 				}
3612 
3613 				if (offsetC < offsetB) {
3614 					return -1; // before
3615 				}
3616 
3617 				return 1; // after
3618 			}
3619 
3620 			// In the fourth case, none of three other cases hold: the containers of A and B
3621 			// are siblings or descendants of sibling nodes. In this case, A is before B if
3622 			// the container of A is before the container of B in a pre-order traversal of the
3623 			// Ranges' context tree and A is after B otherwise.
3624 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
3625 			childA = containerA;
3626 
3627 			while (childA && childA.parentNode != cmnRoot) {
3628 				childA = childA.parentNode;
3629 			}
3630 
3631 			if (!childA) {
3632 				childA = cmnRoot;
3633 			}
3634 
3635 			childB = containerB;
3636 			while (childB && childB.parentNode != cmnRoot) {
3637 				childB = childB.parentNode;
3638 			}
3639 
3640 			if (!childB) {
3641 				childB = cmnRoot;
3642 			}
3643 
3644 			if (childA == childB) {
3645 				return 0; // equal
3646 			}
3647 
3648 			n = cmnRoot.firstChild;
3649 			while (n) {
3650 				if (n == childA) {
3651 					return -1; // before
3652 				}
3653 
3654 				if (n == childB) {
3655 					return 1; // after
3656 				}
3657 
3658 				n = n.nextSibling;
3659 			}
3660 		}
3661 
3662 		function _setEndPoint(st, n, o) {
3663 			var ec, sc;
3664 
3665 			if (st) {
3666 				self[START_CONTAINER] = n;
3667 				self[START_OFFSET] = o;
3668 			} else {
3669 				self[END_CONTAINER] = n;
3670 				self[END_OFFSET] = o;
3671 			}
3672 
3673 			// If one boundary-point of a Range is set to have a root container
3674 			// other than the current one for the Range, the Range is collapsed to
3675 			// the new position. This enforces the restriction that both boundary-
3676 			// points of a Range must have the same root container.
3677 			ec = self[END_CONTAINER];
3678 			while (ec.parentNode) {
3679 				ec = ec.parentNode;
3680 			}
3681 
3682 			sc = self[START_CONTAINER];
3683 			while (sc.parentNode) {
3684 				sc = sc.parentNode;
3685 			}
3686 
3687 			if (sc == ec) {
3688 				// The start position of a Range is guaranteed to never be after the
3689 				// end position. To enforce this restriction, if the start is set to
3690 				// be at a position after the end, the Range is collapsed to that
3691 				// position.
3692 				if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
3693 					self.collapse(st);
3694 				}
3695 			} else {
3696 				self.collapse(st);
3697 			}
3698 
3699 			self.collapsed = _isCollapsed();
3700 			self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
3701 		}
3702 
3703 		function _traverse(how) {
3704 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
3705 
3706 			if (self[START_CONTAINER] == self[END_CONTAINER]) {
3707 				return _traverseSameContainer(how);
3708 			}
3709 
3710 			for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
3711 				if (p == self[START_CONTAINER]) {
3712 					return _traverseCommonStartContainer(c, how);
3713 				}
3714 
3715 				++endContainerDepth;
3716 			}
3717 
3718 			for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
3719 				if (p == self[END_CONTAINER]) {
3720 					return _traverseCommonEndContainer(c, how);
3721 				}
3722 
3723 				++startContainerDepth;
3724 			}
3725 
3726 			depthDiff = startContainerDepth - endContainerDepth;
3727 
3728 			startNode = self[START_CONTAINER];
3729 			while (depthDiff > 0) {
3730 				startNode = startNode.parentNode;
3731 				depthDiff--;
3732 			}
3733 
3734 			endNode = self[END_CONTAINER];
3735 			while (depthDiff < 0) {
3736 				endNode = endNode.parentNode;
3737 				depthDiff++;
3738 			}
3739 
3740 			// ascend the ancestor hierarchy until we have a common parent.
3741 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
3742 				startNode = sp;
3743 				endNode = ep;
3744 			}
3745 
3746 			return _traverseCommonAncestors(startNode, endNode, how);
3747 		}
3748 
3749 		function _traverseSameContainer(how) {
3750 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
3751 
3752 			if (how != DELETE) {
3753 				frag = createDocumentFragment();
3754 			}
3755 
3756 			// If selection is empty, just return the fragment
3757 			if (self[START_OFFSET] == self[END_OFFSET]) {
3758 				return frag;
3759 			}
3760 
3761 			// Text node needs special case handling
3762 			if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
3763 				// get the substring
3764 				s = self[START_CONTAINER].nodeValue;
3765 				sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
3766 
3767 				// set the original text node to its new value
3768 				if (how != CLONE) {
3769 					n = self[START_CONTAINER];
3770 					start = self[START_OFFSET];
3771 					len = self[END_OFFSET] - self[START_OFFSET];
3772 
3773 					if (start === 0 && len >= n.nodeValue.length - 1) {
3774 						n.parentNode.removeChild(n);
3775 					} else {
3776 						n.deleteData(start, len);
3777 					}
3778 
3779 					// Nothing is partially selected, so collapse to start point
3780 					self.collapse(TRUE);
3781 				}
3782 
3783 				if (how == DELETE) {
3784 					return;
3785 				}
3786 
3787 				if (sub.length > 0) {
3788 					frag.appendChild(doc.createTextNode(sub));
3789 				}
3790 
3791 				return frag;
3792 			}
3793 
3794 			// Copy nodes between the start/end offsets.
3795 			n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
3796 			cnt = self[END_OFFSET] - self[START_OFFSET];
3797 
3798 			while (n && cnt > 0) {
3799 				sibling = n.nextSibling;
3800 				xferNode = _traverseFullySelected(n, how);
3801 
3802 				if (frag) {
3803 					frag.appendChild(xferNode);
3804 				}
3805 
3806 				--cnt;
3807 				n = sibling;
3808 			}
3809 
3810 			// Nothing is partially selected, so collapse to start point
3811 			if (how != CLONE) {
3812 				self.collapse(TRUE);
3813 			}
3814 
3815 			return frag;
3816 		}
3817 
3818 		function _traverseCommonStartContainer(endAncestor, how) {
3819 			var frag, n, endIdx, cnt, sibling, xferNode;
3820 
3821 			if (how != DELETE) {
3822 				frag = createDocumentFragment();
3823 			}
3824 
3825 			n = _traverseRightBoundary(endAncestor, how);
3826 
3827 			if (frag) {
3828 				frag.appendChild(n);
3829 			}
3830 
3831 			endIdx = nodeIndex(endAncestor);
3832 			cnt = endIdx - self[START_OFFSET];
3833 
3834 			if (cnt <= 0) {
3835 				// Collapse to just before the endAncestor, which
3836 				// is partially selected.
3837 				if (how != CLONE) {
3838 					self.setEndBefore(endAncestor);
3839 					self.collapse(FALSE);
3840 				}
3841 
3842 				return frag;
3843 			}
3844 
3845 			n = endAncestor.previousSibling;
3846 			while (cnt > 0) {
3847 				sibling = n.previousSibling;
3848 				xferNode = _traverseFullySelected(n, how);
3849 
3850 				if (frag) {
3851 					frag.insertBefore(xferNode, frag.firstChild);
3852 				}
3853 
3854 				--cnt;
3855 				n = sibling;
3856 			}
3857 
3858 			// Collapse to just before the endAncestor, which
3859 			// is partially selected.
3860 			if (how != CLONE) {
3861 				self.setEndBefore(endAncestor);
3862 				self.collapse(FALSE);
3863 			}
3864 
3865 			return frag;
3866 		}
3867 
3868 		function _traverseCommonEndContainer(startAncestor, how) {
3869 			var frag, startIdx, n, cnt, sibling, xferNode;
3870 
3871 			if (how != DELETE) {
3872 				frag = createDocumentFragment();
3873 			}
3874 
3875 			n = _traverseLeftBoundary(startAncestor, how);
3876 			if (frag) {
3877 				frag.appendChild(n);
3878 			}
3879 
3880 			startIdx = nodeIndex(startAncestor);
3881 			++startIdx; // Because we already traversed it
3882 
3883 			cnt = self[END_OFFSET] - startIdx;
3884 			n = startAncestor.nextSibling;
3885 			while (n && cnt > 0) {
3886 				sibling = n.nextSibling;
3887 				xferNode = _traverseFullySelected(n, how);
3888 
3889 				if (frag) {
3890 					frag.appendChild(xferNode);
3891 				}
3892 
3893 				--cnt;
3894 				n = sibling;
3895 			}
3896 
3897 			if (how != CLONE) {
3898 				self.setStartAfter(startAncestor);
3899 				self.collapse(TRUE);
3900 			}
3901 
3902 			return frag;
3903 		}
3904 
3905 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
3906 			var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
3907 
3908 			if (how != DELETE) {
3909 				frag = createDocumentFragment();
3910 			}
3911 
3912 			n = _traverseLeftBoundary(startAncestor, how);
3913 			if (frag) {
3914 				frag.appendChild(n);
3915 			}
3916 
3917 			startOffset = nodeIndex(startAncestor);
3918 			endOffset = nodeIndex(endAncestor);
3919 			++startOffset;
3920 
3921 			cnt = endOffset - startOffset;
3922 			sibling = startAncestor.nextSibling;
3923 
3924 			while (cnt > 0) {
3925 				nextSibling = sibling.nextSibling;
3926 				n = _traverseFullySelected(sibling, how);
3927 
3928 				if (frag) {
3929 					frag.appendChild(n);
3930 				}
3931 
3932 				sibling = nextSibling;
3933 				--cnt;
3934 			}
3935 
3936 			n = _traverseRightBoundary(endAncestor, how);
3937 
3938 			if (frag) {
3939 				frag.appendChild(n);
3940 			}
3941 
3942 			if (how != CLONE) {
3943 				self.setStartAfter(startAncestor);
3944 				self.collapse(TRUE);
3945 			}
3946 
3947 			return frag;
3948 		}
3949 
3950 		function _traverseRightBoundary(root, how) {
3951 			var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
3952 			var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
3953 
3954 			if (next == root) {
3955 				return _traverseNode(next, isFullySelected, FALSE, how);
3956 			}
3957 
3958 			parent = next.parentNode;
3959 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
3960 
3961 			while (parent) {
3962 				while (next) {
3963 					prevSibling = next.previousSibling;
3964 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
3965 
3966 					if (how != DELETE) {
3967 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
3968 					}
3969 
3970 					isFullySelected = TRUE;
3971 					next = prevSibling;
3972 				}
3973 
3974 				if (parent == root) {
3975 					return clonedParent;
3976 				}
3977 
3978 				next = parent.previousSibling;
3979 				parent = parent.parentNode;
3980 
3981 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
3982 
3983 				if (how != DELETE) {
3984 					clonedGrandParent.appendChild(clonedParent);
3985 				}
3986 
3987 				clonedParent = clonedGrandParent;
3988 			}
3989 		}
3990 
3991 		function _traverseLeftBoundary(root, how) {
3992 			var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
3993 			var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
3994 
3995 			if (next == root) {
3996 				return _traverseNode(next, isFullySelected, TRUE, how);
3997 			}
3998 
3999 			parent = next.parentNode;
4000 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
4001 
4002 			while (parent) {
4003 				while (next) {
4004 					nextSibling = next.nextSibling;
4005 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
4006 
4007 					if (how != DELETE) {
4008 						clonedParent.appendChild(clonedChild);
4009 					}
4010 
4011 					isFullySelected = TRUE;
4012 					next = nextSibling;
4013 				}
4014 
4015 				if (parent == root) {
4016 					return clonedParent;
4017 				}
4018 
4019 				next = parent.nextSibling;
4020 				parent = parent.parentNode;
4021 
4022 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
4023 
4024 				if (how != DELETE) {
4025 					clonedGrandParent.appendChild(clonedParent);
4026 				}
4027 
4028 				clonedParent = clonedGrandParent;
4029 			}
4030 		}
4031 
4032 		function _traverseNode(n, isFullySelected, isLeft, how) {
4033 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
4034 
4035 			if (isFullySelected) {
4036 				return _traverseFullySelected(n, how);
4037 			}
4038 
4039 			if (n.nodeType == 3 /* TEXT_NODE */) {
4040 				txtValue = n.nodeValue;
4041 
4042 				if (isLeft) {
4043 					offset = self[START_OFFSET];
4044 					newNodeValue = txtValue.substring(offset);
4045 					oldNodeValue = txtValue.substring(0, offset);
4046 				} else {
4047 					offset = self[END_OFFSET];
4048 					newNodeValue = txtValue.substring(0, offset);
4049 					oldNodeValue = txtValue.substring(offset);
4050 				}
4051 
4052 				if (how != CLONE) {
4053 					n.nodeValue = oldNodeValue;
4054 				}
4055 
4056 				if (how == DELETE) {
4057 					return;
4058 				}
4059 
4060 				newNode = dom.clone(n, FALSE);
4061 				newNode.nodeValue = newNodeValue;
4062 
4063 				return newNode;
4064 			}
4065 
4066 			if (how == DELETE) {
4067 				return;
4068 			}
4069 
4070 			return dom.clone(n, FALSE);
4071 		}
4072 
4073 		function _traverseFullySelected(n, how) {
4074 			if (how != DELETE) {
4075 				return how == CLONE ? dom.clone(n, TRUE) : n;
4076 			}
4077 
4078 			n.parentNode.removeChild(n);
4079 		}
4080 
4081 		function toStringIE() {
4082 			return dom.create('body', null, cloneContents()).outerText;
4083 		}
4084 
4085 		extend(self, {
4086 			// Inital states
4087 			startContainer: doc,
4088 			startOffset: 0,
4089 			endContainer: doc,
4090 			endOffset: 0,
4091 			collapsed: TRUE,
4092 			commonAncestorContainer: doc,
4093 
4094 			// Range constants
4095 			START_TO_START: 0,
4096 			START_TO_END: 1,
4097 			END_TO_END: 2,
4098 			END_TO_START: 3,
4099 
4100 			// Public methods
4101 			setStart: setStart,
4102 			setEnd: setEnd,
4103 			setStartBefore: setStartBefore,
4104 			setStartAfter: setStartAfter,
4105 			setEndBefore: setEndBefore,
4106 			setEndAfter: setEndAfter,
4107 			collapse: collapse,
4108 			selectNode: selectNode,
4109 			selectNodeContents: selectNodeContents,
4110 			compareBoundaryPoints: compareBoundaryPoints,
4111 			deleteContents: deleteContents,
4112 			extractContents: extractContents,
4113 			cloneContents: cloneContents,
4114 			insertNode: insertNode,
4115 			surroundContents: surroundContents,
4116 			cloneRange: cloneRange,
4117 			toStringIE: toStringIE
4118 		});
4119 
4120 		return self;
4121 	}
4122 
4123 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
4124 	Range.prototype.toString = function() {
4125 		return this.toStringIE();
4126 	};
4127 
4128 	return Range;
4129 });
4130 
4131 // Included from: js/tinymce/classes/html/Entities.js
4132 
4133 /**
4134  * Entities.js
4135  *
4136  * Copyright, Moxiecode Systems AB
4137  * Released under LGPL License.
4138  *
4139  * License: http://www.tinymce.com/license
4140  * Contributing: http://www.tinymce.com/contributing
4141  */
4142 
4143 /*jshint bitwise:false */
4144 /*eslint no-bitwise:0 */
4145 
4146 /**
4147  * Entity encoder class.
4148  *
4149  * @class tinymce.html.Entities
4150  * @static
4151  * @version 3.4
4152  */
4153 define("tinymce/html/Entities", [
4154 	"tinymce/util/Tools"
4155 ], function(Tools) {
4156 	var makeMap = Tools.makeMap;
4157 
4158 	var namedEntities, baseEntities, reverseEntities,
4159 		attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
4160 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
4161 		rawCharsRegExp = /[<>&\"\']/g,
4162 		entityRegExp = /&(#x|#)?([\w]+);/g,
4163 		asciiMap = {
4164 			128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
4165 			135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
4166 			142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
4167 			150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
4168 			156: "\u0153", 158: "\u017E", 159: "\u0178"
4169 		};
4170 
4171 	// Raw entities
4172 	baseEntities = {
4173 		'\"': '"', // Needs to be escaped since the YUI compressor would otherwise break the code
4174 		"'": ''',
4175 		'<': '<',
4176 		'>': '>',
4177 		'&': '&',
4178 		'\u0060': '`'
4179 	};
4180 
4181 	// Reverse lookup table for raw entities
4182 	reverseEntities = {
4183 		'<': '<',
4184 		'>': '>',
4185 		'&': '&',
4186 		'"': '"',
4187 		''': "'"
4188 	};
4189 
4190 	// Decodes text by using the browser
4191 	function nativeDecode(text) {
4192 		var elm;
4193 
4194 		elm = document.createElement("div");
4195 		elm.innerHTML = text;
4196 
4197 		return elm.textContent || elm.innerText || text;
4198 	}
4199 
4200 	// Build a two way lookup table for the entities
4201 	function buildEntitiesLookup(items, radix) {
4202 		var i, chr, entity, lookup = {};
4203 
4204 		if (items) {
4205 			items = items.split(',');
4206 			radix = radix || 10;
4207 
4208 			// Build entities lookup table
4209 			for (i = 0; i < items.length; i += 2) {
4210 				chr = String.fromCharCode(parseInt(items[i], radix));
4211 
4212 				// Only add non base entities
4213 				if (!baseEntities[chr]) {
4214 					entity = '&' + items[i + 1] + ';';
4215 					lookup[chr] = entity;
4216 					lookup[entity] = chr;
4217 				}
4218 			}
4219 
4220 			return lookup;
4221 		}
4222 	}
4223 
4224 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
4225 	namedEntities = buildEntitiesLookup(
4226 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
4227 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
4228 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
4229 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
4230 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
4231 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
4232 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
4233 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
4234 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
4235 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
4236 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
4237 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
4238 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
4239 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
4240 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
4241 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
4242 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
4243 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
4244 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
4245 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
4246 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
4247 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
4248 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
4249 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
4250 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
4251 
4252 	var Entities = {
4253 		/**
4254 		 * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
4255 		 *
4256 		 * @method encodeRaw
4257 		 * @param {String} text Text to encode.
4258 		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4259 		 * @return {String} Entity encoded text.
4260 		 */
4261 		encodeRaw: function(text, attr) {
4262 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4263 				return baseEntities[chr] || chr;
4264 			});
4265 		},
4266 
4267 		/**
4268 		 * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
4269 		 * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
4270 		 * and is exposed as the DOMUtils.encode function.
4271 		 *
4272 		 * @method encodeAllRaw
4273 		 * @param {String} text Text to encode.
4274 		 * @return {String} Entity encoded text.
4275 		 */
4276 		encodeAllRaw: function(text) {
4277 			return ('' + text).replace(rawCharsRegExp, function(chr) {
4278 				return baseEntities[chr] || chr;
4279 			});
4280 		},
4281 
4282 		/**
4283 		 * Encodes the specified string using numeric entities. The core entities will be
4284 		 * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
4285 		 *
4286 		 * @method encodeNumeric
4287 		 * @param {String} text Text to encode.
4288 		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4289 		 * @return {String} Entity encoded text.
4290 		 */
4291 		encodeNumeric: function(text, attr) {
4292 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4293 				// Multi byte sequence convert it to a single entity
4294 				if (chr.length > 1) {
4295 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
4296 				}
4297 
4298 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
4299 			});
4300 		},
4301 
4302 		/**
4303 		 * Encodes the specified string using named entities. The core entities will be encoded
4304 		 * as named ones but all non lower ascii characters will be encoded into named entities.
4305 		 *
4306 		 * @method encodeNamed
4307 		 * @param {String} text Text to encode.
4308 		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4309 		 * @param {Object} entities Optional parameter with entities to use.
4310 		 * @return {String} Entity encoded text.
4311 		 */
4312 		encodeNamed: function(text, attr, entities) {
4313 			entities = entities || namedEntities;
4314 
4315 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4316 				return baseEntities[chr] || entities[chr] || chr;
4317 			});
4318 		},
4319 
4320 		/**
4321 		 * Returns an encode function based on the name(s) and it's optional entities.
4322 		 *
4323 		 * @method getEncodeFunc
4324 		 * @param {String} name Comma separated list of encoders for example named,numeric.
4325 		 * @param {String} entities Optional parameter with entities to use instead of the built in set.
4326 		 * @return {function} Encode function to be used.
4327 		 */
4328 		getEncodeFunc: function(name, entities) {
4329 			entities = buildEntitiesLookup(entities) || namedEntities;
4330 
4331 			function encodeNamedAndNumeric(text, attr) {
4332 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4333 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
4334 				});
4335 			}
4336 
4337 			function encodeCustomNamed(text, attr) {
4338 				return Entities.encodeNamed(text, attr, entities);
4339 			}
4340 
4341 			// Replace + with , to be compatible with previous TinyMCE versions
4342 			name = makeMap(name.replace(/\+/g, ','));
4343 
4344 			// Named and numeric encoder
4345 			if (name.named && name.numeric) {
4346 				return encodeNamedAndNumeric;
4347 			}
4348 
4349 			// Named encoder
4350 			if (name.named) {
4351 				// Custom names
4352 				if (entities) {
4353 					return encodeCustomNamed;
4354 				}
4355 
4356 				return Entities.encodeNamed;
4357 			}
4358 
4359 			// Numeric
4360 			if (name.numeric) {
4361 				return Entities.encodeNumeric;
4362 			}
4363 
4364 			// Raw encoder
4365 			return Entities.encodeRaw;
4366 		},
4367 
4368 		/**
4369 		 * Decodes the specified string, this will replace entities with raw UTF characters.
4370 		 *
4371 		 * @method decode
4372 		 * @param {String} text Text to entity decode.
4373 		 * @return {String} Entity decoded string.
4374 		 */
4375 		decode: function(text) {
4376 			return text.replace(entityRegExp, function(all, numeric, value) {
4377 				if (numeric) {
4378 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
4379 
4380 					// Support upper UTF
4381 					if (value > 0xFFFF) {
4382 						value -= 0x10000;
4383 
4384 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
4385 					} else {
4386 						return asciiMap[value] || String.fromCharCode(value);
4387 					}
4388 				}
4389 
4390 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
4391 			});
4392 		}
4393 	};
4394 
4395 	return Entities;
4396 });
4397 
4398 // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
4399 
4400 /**
4401  * StyleSheetLoader.js
4402  *
4403  * Copyright, Moxiecode Systems AB
4404  * Released under LGPL License.
4405  *
4406  * License: http://www.tinymce.com/license
4407  * Contributing: http://www.tinymce.com/contributing
4408  */
4409 
4410 /**
4411  * This class handles loading of external stylesheets and fires events when these are loaded.
4412  *
4413  * @class tinymce.dom.StyleSheetLoader
4414  * @private
4415  */
4416 define("tinymce/dom/StyleSheetLoader", [], function() {
4417 	"use strict";
4418 
4419 	return function(document, settings) {
4420 		var idCount = 0, loadedStates = {}, maxLoadTime;
4421 
4422 		settings = settings || {};
4423 		maxLoadTime = settings.maxLoadTime || 5000;
4424 
4425 		function appendToHead(node) {
4426 			document.getElementsByTagName('head')[0].appendChild(node);
4427 		}
4428 
4429 		/**
4430 		 * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
4431 		 *
4432 		 * @method load
4433 		 * @param {String} url Url to be loaded.
4434 		 * @param {Function} loadedCallback Callback to be executed when loaded.
4435 		 * @param {Function} errorCallback Callback to be executed when failed loading.
4436 		 */
4437 		function load(url, loadedCallback, errorCallback) {
4438 			var link, style, startTime, state;
4439 
4440 			function passed() {
4441 				var callbacks = state.passed, i = callbacks.length;
4442 
4443 				while (i--) {
4444 					callbacks[i]();
4445 				}
4446 
4447 				state.status = 2;
4448 				state.passed = [];
4449 				state.failed = [];
4450 			}
4451 
4452 			function failed() {
4453 				var callbacks = state.failed, i = callbacks.length;
4454 
4455 				while (i--) {
4456 					callbacks[i]();
4457 				}
4458 
4459 				state.status = 3;
4460 				state.passed = [];
4461 				state.failed = [];
4462 			}
4463 
4464 			// Sniffs for older WebKit versions that have the link.onload but a broken one
4465 			function isOldWebKit() {
4466 				var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
4467 				return !!(webKitChunks && webKitChunks[1] < 536);
4468 			}
4469 
4470 			// Calls the waitCallback until the test returns true or the timeout occurs
4471 			function wait(testCallback, waitCallback) {
4472 				if (!testCallback()) {
4473 					// Wait for timeout
4474 					if ((new Date().getTime()) - startTime < maxLoadTime) {
4475 						window.setTimeout(waitCallback, 0);
4476 					} else {
4477 						failed();
4478 					}
4479 				}
4480 			}
4481 
4482 			// Workaround for WebKit that doesn't properly support the onload event for link elements
4483 			// Or WebKit that fires the onload event before the StyleSheet is added to the document
4484 			function waitForWebKitLinkLoaded() {
4485 				wait(function() {
4486 					var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
4487 
4488 					while (i--) {
4489 						styleSheet = styleSheets[i];
4490 						owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
4491 						if (owner && owner.id === link.id) {
4492 							passed();
4493 							return true;
4494 						}
4495 					}
4496 				}, waitForWebKitLinkLoaded);
4497 			}
4498 
4499 			// Workaround for older Geckos that doesn't have any onload event for StyleSheets
4500 			function waitForGeckoLinkLoaded() {
4501 				wait(function() {
4502 					try {
4503 						// Accessing the cssRules will throw an exception until the CSS file is loaded
4504 						var cssRules = style.sheet.cssRules;
4505 						passed();
4506 						return !!cssRules;
4507 					} catch (ex) {
4508 						// Ignore
4509 					}
4510 				}, waitForGeckoLinkLoaded);
4511 			}
4512 
4513 			if (!loadedStates[url]) {
4514 				state = {
4515 					passed: [],
4516 					failed: []
4517 				};
4518 
4519 				loadedStates[url] = state;
4520 			} else {
4521 				state = loadedStates[url];
4522 			}
4523 
4524 			if (loadedCallback) {
4525 				state.passed.push(loadedCallback);
4526 			}
4527 
4528 			if (errorCallback) {
4529 				state.failed.push(errorCallback);
4530 			}
4531 
4532 			// Is loading wait for it to pass
4533 			if (state.status == 1) {
4534 				return;
4535 			}
4536 
4537 			// Has finished loading and was success
4538 			if (state.status == 2) {
4539 				passed();
4540 				return;
4541 			}
4542 
4543 			// Has finished loading and was a failure
4544 			if (state.status == 3) {
4545 				failed();
4546 				return;
4547 			}
4548 
4549 			// Start loading
4550 			state.status = 1;
4551 			link = document.createElement('link');
4552 			link.rel = 'stylesheet';
4553 			link.type = 'text/css';
4554 			link.id = 'u' + (idCount++);
4555 			link.async = false;
4556 			link.defer = false;
4557 			startTime = new Date().getTime();
4558 
4559 			// Feature detect onload on link element and sniff older webkits since it has an broken onload event
4560 			if ("onload" in link && !isOldWebKit()) {
4561 				link.onload = waitForWebKitLinkLoaded;
4562 				link.onerror = failed;
4563 			} else {
4564 				// Sniff for old Firefox that doesn't support the onload event on link elements
4565 				// TODO: Remove this in the future when everyone uses modern browsers
4566 				if (navigator.userAgent.indexOf("Firefox") > 0) {
4567 					style = document.createElement('style');
4568 					style.textContent = '@import "' + url + '"';
4569 					waitForGeckoLinkLoaded();
4570 					appendToHead(style);
4571 					return;
4572 				} else {
4573 					// Use the id owner on older webkits
4574 					waitForWebKitLinkLoaded();
4575 				}
4576 			}
4577 
4578 			appendToHead(link);
4579 			link.href = url;
4580 		}
4581 
4582 		this.load = load;
4583 	};
4584 });
4585 
4586 // Included from: js/tinymce/classes/dom/DOMUtils.js
4587 
4588 /**
4589  * DOMUtils.js
4590  *
4591  * Copyright, Moxiecode Systems AB
4592  * Released under LGPL License.
4593  *
4594  * License: http://www.tinymce.com/license
4595  * Contributing: http://www.tinymce.com/contributing
4596  */
4597 
4598 /**
4599  * Utility class for various DOM manipulation and retrieval functions.
4600  *
4601  * @class tinymce.dom.DOMUtils
4602  * @example
4603  * // Add a class to an element by id in the page
4604  * tinymce.DOM.addClass('someid', 'someclass');
4605  *
4606  * // Add a class to an element by id inside the editor
4607  * tinymce.activeEditor.dom.addClass('someid', 'someclass');
4608  */
4609 define("tinymce/dom/DOMUtils", [
4610 	"tinymce/dom/Sizzle",
4611 	"tinymce/dom/DomQuery",
4612 	"tinymce/html/Styles",
4613 	"tinymce/dom/EventUtils",
4614 	"tinymce/dom/TreeWalker",
4615 	"tinymce/dom/Range",
4616 	"tinymce/html/Entities",
4617 	"tinymce/Env",
4618 	"tinymce/util/Tools",
4619 	"tinymce/dom/StyleSheetLoader"
4620 ], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
4621 	// Shorten names
4622 	var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
4623 	var isIE = Env.ie;
4624 	var simpleSelectorRe = /^([a-z0-9],?)+$/i;
4625 	var whiteSpaceRegExp = /^[ \t\r\n]*$/;
4626 
4627 	function setupAttrHooks(domUtils, settings) {
4628 		var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;
4629 
4630 		keepUrlHook = {
4631 			set: function($elm, value, name) {
4632 				if (settings.url_converter) {
4633 					value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
4634 				}
4635 
4636 				$elm.attr('data-mce-' + name, value).attr(name, value);
4637 			},
4638 
4639 			get: function($elm, name) {
4640 				return $elm.attr('data-mce-' + name) || $elm.attr(name);
4641 			}
4642 		};
4643 
4644 		attrHooks = {
4645 			style: {
4646 				set: function($elm, value) {
4647 					if (value !== null && typeof value === 'object') {
4648 						$elm.css(value);
4649 						return;
4650 					}
4651 
4652 					if (keepValues) {
4653 						$elm.attr('data-mce-style', value);
4654 					}
4655 
4656 					$elm.attr('style', value);
4657 				},
4658 
4659 				get: function($elm) {
4660 					var value = $elm.attr('data-mce-style') || $elm.attr('style');
4661 
4662 					value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
4663 
4664 					return value;
4665 				}
4666 			}
4667 		};
4668 
4669 		if (keepValues) {
4670 			attrHooks.href = attrHooks.src = keepUrlHook;
4671 		}
4672 
4673 		return attrHooks;
4674 	}
4675 
4676 	/**
4677 	 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
4678 	 *
4679 	 * @constructor
4680 	 * @method DOMUtils
4681 	 * @param {Document} d Document reference to bind the utility class to.
4682 	 * @param {settings} s Optional settings collection.
4683 	 */
4684 	function DOMUtils(doc, settings) {
4685 		var self = this, blockElementsMap;
4686 
4687 		self.doc = doc;
4688 		self.win = window;
4689 		self.files = {};
4690 		self.counter = 0;
4691 		self.stdMode = !isIE || doc.documentMode >= 8;
4692 		self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
4693 		self.styleSheetLoader = new StyleSheetLoader(doc);
4694 		self.boundEvents = [];
4695 		self.settings = settings = settings || {};
4696 		self.schema = settings.schema;
4697 		self.styles = new Styles({
4698 			url_converter: settings.url_converter,
4699 			url_converter_scope: settings.url_converter_scope
4700 		}, settings.schema);
4701 
4702 		self.fixDoc(doc);
4703 		self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
4704 		self.attrHooks = setupAttrHooks(self, settings);
4705 		blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
4706 		self.$ = $.overrideDefaults(function() {
4707 			return {
4708 				context: doc,
4709 				element: self.getRoot()
4710 			};
4711 		});
4712 
4713 		/**
4714 		 * Returns true/false if the specified element is a block element or not.
4715 		 *
4716 		 * @method isBlock
4717 		 * @param {Node/String} node Element/Node to check.
4718 		 * @return {Boolean} True/False state if the node is a block element or not.
4719 		 */
4720 		self.isBlock = function(node) {
4721 			// Fix for #5446
4722 			if (!node) {
4723 				return false;
4724 			}
4725 
4726 			// This function is called in module pattern style since it might be executed with the wrong this scope
4727 			var type = node.nodeType;
4728 
4729 			// If it's a node then check the type and use the nodeName
4730 			if (type) {
4731 				return !!(type === 1 && blockElementsMap[node.nodeName]);
4732 			}
4733 
4734 			return !!blockElementsMap[node];
4735 		};
4736 	}
4737 
4738 	DOMUtils.prototype = {
4739 		$$: function(elm) {
4740 			if (typeof elm == 'string') {
4741 				elm = this.get(elm);
4742 			}
4743 
4744 			return this.$(elm);
4745 		},
4746 
4747 		root: null,
4748 
4749 		fixDoc: function(doc) {
4750 			var settings = this.settings, name;
4751 
4752 			if (isIE && settings.schema) {
4753 				// Add missing HTML 4/5 elements to IE
4754 				('abbr article aside audio canvas ' +
4755 				'details figcaption figure footer ' +
4756 				'header hgroup mark menu meter nav ' +
4757 				'output progress section summary ' +
4758 				'time video').replace(/\w+/g, function(name) {
4759 					doc.createElement(name);
4760 				});
4761 
4762 				// Create all custom elements
4763 				for (name in settings.schema.getCustomElements()) {
4764 					doc.createElement(name);
4765 				}
4766 			}
4767 		},
4768 
4769 		clone: function(node, deep) {
4770 			var self = this, clone, doc;
4771 
4772 			// TODO: Add feature detection here in the future
4773 			if (!isIE || node.nodeType !== 1 || deep) {
4774 				return node.cloneNode(deep);
4775 			}
4776 
4777 			doc = self.doc;
4778 
4779 			// Make a HTML5 safe shallow copy
4780 			if (!deep) {
4781 				clone = doc.createElement(node.nodeName);
4782 
4783 				// Copy attribs
4784 				each(self.getAttribs(node), function(attr) {
4785 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
4786 				});
4787 
4788 				return clone;
4789 			}
4790 
4791 			return clone.firstChild;
4792 		},
4793 
4794 		/**
4795 		 * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
4796 		 * go above the point of this root node.
4797 		 *
4798 		 * @method getRoot
4799 		 * @return {Element} Root element for the utility class.
4800 		 */
4801 		getRoot: function() {
4802 			var self = this;
4803 
4804 			return self.settings.root_element || self.doc.body;
4805 		},
4806 
4807 		/**
4808 		 * Returns the viewport of the window.
4809 		 *
4810 		 * @method getViewPort
4811 		 * @param {Window} win Optional window to get viewport of.
4812 		 * @return {Object} Viewport object with fields x, y, w and h.
4813 		 */
4814 		getViewPort: function(win) {
4815 			var doc, rootElm;
4816 
4817 			win = !win ? this.win : win;
4818 			doc = win.document;
4819 			rootElm = this.boxModel ? doc.documentElement : doc.body;
4820 
4821 			// Returns viewport size excluding scrollbars
4822 			return {
4823 				x: win.pageXOffset || rootElm.scrollLeft,
4824 				y: win.pageYOffset || rootElm.scrollTop,
4825 				w: win.innerWidth || rootElm.clientWidth,
4826 				h: win.innerHeight || rootElm.clientHeight
4827 			};
4828 		},
4829 
4830 		/**
4831 		 * Returns the rectangle for a specific element.
4832 		 *
4833 		 * @method getRect
4834 		 * @param {Element/String} elm Element object or element ID to get rectangle from.
4835 		 * @return {object} Rectangle for specified element object with x, y, w, h fields.
4836 		 */
4837 		getRect: function(elm) {
4838 			var self = this, pos, size;
4839 
4840 			elm = self.get(elm);
4841 			pos = self.getPos(elm);
4842 			size = self.getSize(elm);
4843 
4844 			return {
4845 				x: pos.x, y: pos.y,
4846 				w: size.w, h: size.h
4847 			};
4848 		},
4849 
4850 		/**
4851 		 * Returns the size dimensions of the specified element.
4852 		 *
4853 		 * @method getSize
4854 		 * @param {Element/String} elm Element object or element ID to get rectangle from.
4855 		 * @return {object} Rectangle for specified element object with w, h fields.
4856 		 */
4857 		getSize: function(elm) {
4858 			var self = this, w, h;
4859 
4860 			elm = self.get(elm);
4861 			w = self.getStyle(elm, 'width');
4862 			h = self.getStyle(elm, 'height');
4863 
4864 			// Non pixel value, then force offset/clientWidth
4865 			if (w.indexOf('px') === -1) {
4866 				w = 0;
4867 			}
4868 
4869 			// Non pixel value, then force offset/clientWidth
4870 			if (h.indexOf('px') === -1) {
4871 				h = 0;
4872 			}
4873 
4874 			return {
4875 				w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
4876 				h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
4877 			};
4878 		},
4879 
4880 		/**
4881 		 * Returns a node by the specified selector function. This function will
4882 		 * loop through all parent nodes and call the specified function for each node.
4883 		 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
4884 		 * and the node it found will be returned.
4885 		 *
4886 		 * @method getParent
4887 		 * @param {Node/String} node DOM node to search parents on or ID string.
4888 		 * @param {function} selector Selection function or CSS selector to execute on each node.
4889 		 * @param {Node} root Optional root element, never go below this point.
4890 		 * @return {Node} DOM Node or null if it wasn't found.
4891 		 */
4892 		getParent: function(node, selector, root) {
4893 			return this.getParents(node, selector, root, false);
4894 		},
4895 
4896 		/**
4897 		 * Returns a node list of all parents matching the specified selector function or pattern.
4898 		 * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
4899 		 *
4900 		 * @method getParents
4901 		 * @param {Node/String} node DOM node to search parents on or ID string.
4902 		 * @param {function} selector Selection function to execute on each node or CSS pattern.
4903 		 * @param {Node} root Optional root element, never go below this point.
4904 		 * @return {Array} Array of nodes or null if it wasn't found.
4905 		 */
4906 		getParents: function(node, selector, root, collect) {
4907 			var self = this, selectorVal, result = [];
4908 
4909 			node = self.get(node);
4910 			collect = collect === undefined;
4911 
4912 			// Default root on inline mode
4913 			root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
4914 
4915 			// Wrap node name as func
4916 			if (is(selector, 'string')) {
4917 				selectorVal = selector;
4918 
4919 				if (selector === '*') {
4920 					selector = function(node) {
4921 						return node.nodeType == 1;
4922 					};
4923 				} else {
4924 					selector = function(node) {
4925 						return self.is(node, selectorVal);
4926 					};
4927 				}
4928 			}
4929 
4930 			while (node) {
4931 				if (node == root || !node.nodeType || node.nodeType === 9) {
4932 					break;
4933 				}
4934 
4935 				if (!selector || selector(node)) {
4936 					if (collect) {
4937 						result.push(node);
4938 					} else {
4939 						return node;
4940 					}
4941 				}
4942 
4943 				node = node.parentNode;
4944 			}
4945 
4946 			return collect ? result : null;
4947 		},
4948 
4949 		/**
4950 		 * Returns the specified element by ID or the input element if it isn't a string.
4951 		 *
4952 		 * @method get
4953 		 * @param {String/Element} n Element id to look for or element to just pass though.
4954 		 * @return {Element} Element matching the specified id or null if it wasn't found.
4955 		 */
4956 		get: function(elm) {
4957 			var name;
4958 
4959 			if (elm && this.doc && typeof(elm) == 'string') {
4960 				name = elm;
4961 				elm = this.doc.getElementById(elm);
4962 
4963 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
4964 				if (elm && elm.id !== name) {
4965 					return this.doc.getElementsByName(name)[1];
4966 				}
4967 			}
4968 
4969 			return elm;
4970 		},
4971 
4972 		/**
4973 		 * Returns the next node that matches selector or function
4974 		 *
4975 		 * @method getNext
4976 		 * @param {Node} node Node to find siblings from.
4977 		 * @param {String/function} selector Selector CSS expression or function.
4978 		 * @return {Node} Next node item matching the selector or null if it wasn't found.
4979 		 */
4980 		getNext: function(node, selector) {
4981 			return this._findSib(node, selector, 'nextSibling');
4982 		},
4983 
4984 		/**
4985 		 * Returns the previous node that matches selector or function
4986 		 *
4987 		 * @method getPrev
4988 		 * @param {Node} node Node to find siblings from.
4989 		 * @param {String/function} selector Selector CSS expression or function.
4990 		 * @return {Node} Previous node item matching the selector or null if it wasn't found.
4991 		 */
4992 		getPrev: function(node, selector) {
4993 			return this._findSib(node, selector, 'previousSibling');
4994 		},
4995 
4996 		// #ifndef jquery
4997 
4998 		/**
4999 		 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
5000 		 * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
5001 		 * on more complex patterns.
5002 		 *
5003 		 * @method select
5004 		 * @param {String} selector CSS level 3 pattern to select/find elements by.
5005 		 * @param {Object} scope Optional root element/scope element to search in.
5006 		 * @return {Array} Array with all matched elements.
5007 		 * @example
5008 		 * // Adds a class to all paragraphs in the currently active editor
5009 		 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
5010 		 *
5011 		 * // Adds a class to all spans that have the test class in the currently active editor
5012 		 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
5013 		 */
5014 		select: function(selector, scope) {
5015 			var self = this;
5016 
5017 			/*eslint new-cap:0 */
5018 			return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
5019 		},
5020 
5021 		/**
5022 		 * Returns true/false if the specified element matches the specified css pattern.
5023 		 *
5024 		 * @method is
5025 		 * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
5026 		 * @param {String} selector CSS pattern to match the element against.
5027 		 */
5028 		is: function(elm, selector) {
5029 			var i;
5030 
5031 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5032 			if (elm.length === undefined) {
5033 				// Simple all selector
5034 				if (selector === '*') {
5035 					return elm.nodeType == 1;
5036 				}
5037 
5038 				// Simple selector just elements
5039 				if (simpleSelectorRe.test(selector)) {
5040 					selector = selector.toLowerCase().split(/,/);
5041 					elm = elm.nodeName.toLowerCase();
5042 
5043 					for (i = selector.length - 1; i >= 0; i--) {
5044 						if (selector[i] == elm) {
5045 							return true;
5046 						}
5047 					}
5048 
5049 					return false;
5050 				}
5051 			}
5052 
5053 			// Is non element
5054 			if (elm.nodeType && elm.nodeType != 1) {
5055 				return false;
5056 			}
5057 
5058 			var elms = elm.nodeType ? [elm] : elm;
5059 
5060 			/*eslint new-cap:0 */
5061 			return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
5062 		},
5063 
5064 		// #endif
5065 
5066 		/**
5067 		 * Adds the specified element to another element or elements.
5068 		 *
5069 		 * @method add
5070 		 * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
5071 		 * @param {String/Element} name Name of new element to add or existing element to add.
5072 		 * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
5073 		 * @param {String} html Optional inner HTML contents to add for each element.
5074 		 * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
5075 		 * were passed in.
5076 		 * @example
5077 		 * // Adds a new paragraph to the end of the active editor
5078 		 * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
5079 		 */
5080 		add: function(parentElm, name, attrs, html, create) {
5081 			var self = this;
5082 
5083 			return this.run(parentElm, function(parentElm) {
5084 				var newElm;
5085 
5086 				newElm = is(name, 'string') ? self.doc.createElement(name) : name;
5087 				self.setAttribs(newElm, attrs);
5088 
5089 				if (html) {
5090 					if (html.nodeType) {
5091 						newElm.appendChild(html);
5092 					} else {
5093 						self.setHTML(newElm, html);
5094 					}
5095 				}
5096 
5097 				return !create ? parentElm.appendChild(newElm) : newElm;
5098 			});
5099 		},
5100 
5101 		/**
5102 		 * Creates a new element.
5103 		 *
5104 		 * @method create
5105 		 * @param {String} name Name of new element.
5106 		 * @param {Object} attrs Optional object name/value collection with element attributes.
5107 		 * @param {String} html Optional HTML string to set as inner HTML of the element.
5108 		 * @return {Element} HTML DOM node element that got created.
5109 		 * @example
5110 		 * // Adds an element where the caret/selection is in the active editor
5111 		 * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
5112 		 * tinymce.activeEditor.selection.setNode(el);
5113 		 */
5114 		create: function(name, attrs, html) {
5115 			return this.add(this.doc.createElement(name), name, attrs, html, 1);
5116 		},
5117 
5118 		/**
5119 		 * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
5120 		 *
5121 		 * @method createHTML
5122 		 * @param {String} name Name of new element.
5123 		 * @param {Object} attrs Optional object name/value collection with element attributes.
5124 		 * @param {String} html Optional HTML string to set as inner HTML of the element.
5125 		 * @return {String} String with new HTML element, for example: <a href="#">test</a>.
5126 		 * @example
5127 		 * // Creates a html chunk and inserts it at the current selection/caret location
5128 		 * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
5129 		 */
5130 		createHTML: function(name, attrs, html) {
5131 			var outHtml = '', key;
5132 
5133 			outHtml += '<' + name;
5134 
5135 			for (key in attrs) {
5136 				if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') {
5137 					outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
5138 				}
5139 			}
5140 
5141 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5142 			if (typeof(html) != "undefined") {
5143 				return outHtml + '>' + html + '</' + name + '>';
5144 			}
5145 
5146 			return outHtml + ' />';
5147 		},
5148 
5149 		/**
5150 		 * Creates a document fragment out of the specified HTML string.
5151 		 *
5152 		 * @method createFragment
5153 		 * @param {String} html Html string to create fragment from.
5154 		 * @return {DocumentFragment} Document fragment node.
5155 		 */
5156 		createFragment: function(html) {
5157 			var frag, node, doc = this.doc, container;
5158 
5159 			container = doc.createElement("div");
5160 			frag = doc.createDocumentFragment();
5161 
5162 			if (html) {
5163 				container.innerHTML = html;
5164 			}
5165 
5166 			while ((node = container.firstChild)) {
5167 				frag.appendChild(node);
5168 			}
5169 
5170 			return frag;
5171 		},
5172 
5173 		/**
5174 		 * Removes/deletes the specified element(s) from the DOM.
5175 		 *
5176 		 * @method remove
5177 		 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
5178 		 * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
5179 		 * placed at the location of the removed element.
5180 		 * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
5181 		 * were passed in.
5182 		 * @example
5183 		 * // Removes all paragraphs in the active editor
5184 		 * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
5185 		 *
5186 		 * // Removes an element by id in the document
5187 		 * tinymce.DOM.remove('mydiv');
5188 		 */
5189 		remove: function(node, keepChildren) {
5190 			node = this.$$(node);
5191 
5192 			if (keepChildren) {
5193 				node.each(function() {
5194 					var child;
5195 
5196 					while ((child = this.firstChild)) {
5197 						if (child.nodeType == 3 && child.data.length === 0) {
5198 							this.removeChild(child);
5199 						} else {
5200 							this.parentNode.insertBefore(child, this);
5201 						}
5202 					}
5203 				}).remove();
5204 			} else {
5205 				node.remove();
5206 			}
5207 
5208 			return node.length > 1 ? node.toArray() : node[0];
5209 		},
5210 
5211 		/**
5212 		 * Sets the CSS style value on a HTML element. The name can be a camelcase string
5213 		 * or the CSS style name like background-color.
5214 		 *
5215 		 * @method setStyle
5216 		 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
5217 		 * @param {String} na Name of the style value to set.
5218 		 * @param {String} v Value to set on the style.
5219 		 * @example
5220 		 * // Sets a style value on all paragraphs in the currently active editor
5221 		 * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
5222 		 *
5223 		 * // Sets a style value to an element by id in the current document
5224 		 * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
5225 		 */
5226 		setStyle: function(elm, name, value) {
5227 			elm = this.$$(elm).css(name, value);
5228 
5229 			if (this.settings.update_styles) {
5230 				elm.attr('data-mce-style', null);
5231 			}
5232 		},
5233 
5234 		/**
5235 		 * Returns the current style or runtime/computed value of an element.
5236 		 *
5237 		 * @method getStyle
5238 		 * @param {String/Element} elm HTML element or element id string to get style from.
5239 		 * @param {String} name Style name to return.
5240 		 * @param {Boolean} computed Computed style.
5241 		 * @return {String} Current style or computed style value of an element.
5242 		 */
5243 		getStyle: function(elm, name, computed) {
5244 			elm = this.$$(elm);
5245 
5246 			if (computed) {
5247 				return elm.css(name);
5248 			}
5249 
5250 			// Camelcase it, if needed
5251 			name = name.replace(/-(\D)/g, function(a, b) {
5252 				return b.toUpperCase();
5253 			});
5254 
5255 			if (name == 'float') {
5256 				name = isIE ? 'styleFloat' : 'cssFloat';
5257 			}
5258 
5259 			return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
5260 		},
5261 
5262 		/**
5263 		 * Sets multiple styles on the specified element(s).
5264 		 *
5265 		 * @method setStyles
5266 		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
5267 		 * @param {Object} o Name/Value collection of style items to add to the element(s).
5268 		 * @example
5269 		 * // Sets styles on all paragraphs in the currently active editor
5270 		 * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
5271 		 *
5272 		 * // Sets styles to an element by id in the current document
5273 		 * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
5274 		 */
5275 		setStyles: function(elm, styles) {
5276 			elm = this.$$(elm).css(styles);
5277 
5278 			if (this.settings.update_styles) {
5279 				elm.attr('data-mce-style', null);
5280 			}
5281 		},
5282 
5283 		/**
5284 		 * Removes all attributes from an element or elements.
5285 		 *
5286 		 * @method removeAllAttribs
5287 		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
5288 		 */
5289 		removeAllAttribs: function(e) {
5290 			return this.run(e, function(e) {
5291 				var i, attrs = e.attributes;
5292 				for (i = attrs.length - 1; i >= 0; i--) {
5293 					e.removeAttributeNode(attrs.item(i));
5294 				}
5295 			});
5296 		},
5297 
5298 		/**
5299 		 * Sets the specified attribute of an element or elements.
5300 		 *
5301 		 * @method setAttrib
5302 		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
5303 		 * @param {String} n Name of attribute to set.
5304 		 * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
5305 		 * @example
5306 		 * // Sets class attribute on all paragraphs in the active editor
5307 		 * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
5308 		 *
5309 		 * // Sets class attribute on a specific element in the current page
5310 		 * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
5311 		 */
5312 		setAttrib: function(elm, name, value) {
5313 			var self = this, originalValue, hook, settings = self.settings;
5314 
5315 			if (value === '') {
5316 				value = null;
5317 			}
5318 
5319 			elm = self.$$(elm);
5320 			originalValue = elm.attr(name);
5321 
5322 			if (!elm.length) {
5323 				return;
5324 			}
5325 
5326 			hook = self.attrHooks[name];
5327 			if (hook && hook.set) {
5328 				hook.set(elm, value, name);
5329 			} else {
5330 				elm.attr(name, value);
5331 			}
5332 
5333 			if (originalValue != value && settings.onSetAttrib) {
5334 				settings.onSetAttrib({
5335 					attrElm: elm,
5336 					attrName: name,
5337 					attrValue: value
5338 				});
5339 			}
5340 		},
5341 
5342 		/**
5343 		 * Sets two or more specified attributes of an element or elements.
5344 		 *
5345 		 * @method setAttribs
5346 		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
5347 		 * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
5348 		 * @example
5349 		 * // Sets class and title attributes on all paragraphs in the active editor
5350 		 * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
5351 		 *
5352 		 * // Sets class and title attributes on a specific element in the current page
5353 		 * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
5354 		 */
5355 		setAttribs: function(elm, attrs) {
5356 			var self = this;
5357 
5358 			self.$$(elm).each(function(i, node) {
5359 				each(attrs, function(value, name) {
5360 					self.setAttrib(node, name, value);
5361 				});
5362 			});
5363 		},
5364 
5365 		/**
5366 		 * Returns the specified attribute by name.
5367 		 *
5368 		 * @method getAttrib
5369 		 * @param {String/Element} elm Element string id or DOM element to get attribute from.
5370 		 * @param {String} name Name of attribute to get.
5371 		 * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
5372 		 * @return {String} Attribute value string, default value or null if the attribute wasn't found.
5373 		 */
5374 		getAttrib: function(elm, name, defaultVal) {
5375 			var self = this, hook, value;
5376 
5377 			elm = self.$$(elm);
5378 
5379 			if (elm.length) {
5380 				hook = self.attrHooks[name];
5381 
5382 				if (hook && hook.get) {
5383 					value = hook.get(elm, name);
5384 				} else {
5385 					value = elm.attr(name);
5386 				}
5387 			}
5388 
5389 			if (typeof value == 'undefined') {
5390 				value = defaultVal || '';
5391 			}
5392 
5393 			return value;
5394 		},
5395 
5396 		/**
5397 		 * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
5398 		 *
5399 		 * @method getPos
5400 		 * @param {Element/String} elm HTML element or element id to get x, y position from.
5401 		 * @param {Element} rootElm Optional root element to stop calculations at.
5402 		 * @return {object} Absolute position of the specified element object with x, y fields.
5403 		 */
5404 		getPos: function(elm, rootElm) {
5405 			var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos;
5406 
5407 			elm = self.get(elm);
5408 			rootElm = rootElm || body;
5409 
5410 			if (elm) {
5411 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5412 				// Fallback to offsetParent calculations if the body isn't static better since it stops at the body root
5413 				if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') {
5414 					pos = elm.getBoundingClientRect();
5415 					rootElm = self.boxModel ? doc.documentElement : body;
5416 
5417 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5418 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5419 					x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft;
5420 					y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop;
5421 
5422 					return {x: x, y: y};
5423 				}
5424 
5425 				offsetParent = elm;
5426 				while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5427 					x += offsetParent.offsetLeft || 0;
5428 					y += offsetParent.offsetTop || 0;
5429 					offsetParent = offsetParent.offsetParent;
5430 				}
5431 
5432 				offsetParent = elm.parentNode;
5433 				while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5434 					x -= offsetParent.scrollLeft || 0;
5435 					y -= offsetParent.scrollTop || 0;
5436 					offsetParent = offsetParent.parentNode;
5437 				}
5438 			}
5439 
5440 			return {x: x, y: y};
5441 		},
5442 
5443 		/**
5444 		 * Parses the specified style value into an object collection. This parser will also
5445 		 * merge and remove any redundant items that browsers might have added. It will also convert non-hex
5446 		 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
5447 		 *
5448 		 * @method parseStyle
5449 		 * @param {String} cssText Style value to parse, for example: border:1px solid red;.
5450 		 * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
5451 		 */
5452 		parseStyle: function(cssText) {
5453 			return this.styles.parse(cssText);
5454 		},
5455 
5456 		/**
5457 		 * Serializes the specified style object into a string.
5458 		 *
5459 		 * @method serializeStyle
5460 		 * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
5461 		 * @param {String} name Optional element name.
5462 		 * @return {String} String representation of the style object, for example: border: 1px solid red.
5463 		 */
5464 		serializeStyle: function(styles, name) {
5465 			return this.styles.serialize(styles, name);
5466 		},
5467 
5468 		/**
5469 		 * Adds a style element at the top of the document with the specified cssText content.
5470 		 *
5471 		 * @method addStyle
5472 		 * @param {String} cssText CSS Text style to add to top of head of document.
5473 		 */
5474 		addStyle: function(cssText) {
5475 			var self = this, doc = self.doc, head, styleElm;
5476 
5477 			// Prevent inline from loading the same styles twice
5478 			if (self !== DOMUtils.DOM && doc === document) {
5479 				var addedStyles = DOMUtils.DOM.addedStyles;
5480 
5481 				addedStyles = addedStyles || [];
5482 				if (addedStyles[cssText]) {
5483 					return;
5484 				}
5485 
5486 				addedStyles[cssText] = true;
5487 				DOMUtils.DOM.addedStyles = addedStyles;
5488 			}
5489 
5490 			// Create style element if needed
5491 			styleElm = doc.getElementById('mceDefaultStyles');
5492 			if (!styleElm) {
5493 				styleElm = doc.createElement('style');
5494 				styleElm.id = 'mceDefaultStyles';
5495 				styleElm.type = 'text/css';
5496 
5497 				head = doc.getElementsByTagName('head')[0];
5498 				if (head.firstChild) {
5499 					head.insertBefore(styleElm, head.firstChild);
5500 				} else {
5501 					head.appendChild(styleElm);
5502 				}
5503 			}
5504 
5505 			// Append style data to old or new style element
5506 			if (styleElm.styleSheet) {
5507 				styleElm.styleSheet.cssText += cssText;
5508 			} else {
5509 				styleElm.appendChild(doc.createTextNode(cssText));
5510 			}
5511 		},
5512 
5513 		/**
5514 		 * Imports/loads the specified CSS file into the document bound to the class.
5515 		 *
5516 		 * @method loadCSS
5517 		 * @param {String} u URL to CSS file to load.
5518 		 * @example
5519 		 * // Loads a CSS file dynamically into the current document
5520 		 * tinymce.DOM.loadCSS('somepath/some.css');
5521 		 *
5522 		 * // Loads a CSS file into the currently active editor instance
5523 		 * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
5524 		 *
5525 		 * // Loads a CSS file into an editor instance by id
5526 		 * tinymce.get('someid').dom.loadCSS('somepath/some.css');
5527 		 *
5528 		 * // Loads multiple CSS files into the current document
5529 		 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
5530 		 */
5531 		loadCSS: function(url) {
5532 			var self = this, doc = self.doc, head;
5533 
5534 			// Prevent inline from loading the same CSS file twice
5535 			if (self !== DOMUtils.DOM && doc === document) {
5536 				DOMUtils.DOM.loadCSS(url);
5537 				return;
5538 			}
5539 
5540 			if (!url) {
5541 				url = '';
5542 			}
5543 
5544 			head = doc.getElementsByTagName('head')[0];
5545 
5546 			each(url.split(','), function(url) {
5547 				var link;
5548 
5549 				if (self.files[url]) {
5550 					return;
5551 				}
5552 
5553 				self.files[url] = true;
5554 				link = self.create('link', {rel: 'stylesheet', href: url});
5555 
5556 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5557 				// This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
5558 				// It's ugly but it seems to work fine.
5559 				if (isIE && doc.documentMode && doc.recalc) {
5560 					link.onload = function() {
5561 						if (doc.recalc) {
5562 							doc.recalc();
5563 						}
5564 
5565 						link.onload = null;
5566 					};
5567 				}
5568 
5569 				head.appendChild(link);
5570 			});
5571 		},
5572 
5573 		/**
5574 		 * Adds a class to the specified element or elements.
5575 		 *
5576 		 * @method addClass
5577 		 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
5578 		 * @param {String} cls Class name to add to each element.
5579 		 * @return {String/Array} String with new class value or array with new class values for all elements.
5580 		 * @example
5581 		 * // Adds a class to all paragraphs in the active editor
5582 		 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
5583 		 *
5584 		 * // Adds a class to a specific element in the current page
5585 		 * tinymce.DOM.addClass('mydiv', 'myclass');
5586 		 */
5587 		addClass: function(elm, cls) {
5588 			this.$$(elm).addClass(cls);
5589 		},
5590 
5591 		/**
5592 		 * Removes a class from the specified element or elements.
5593 		 *
5594 		 * @method removeClass
5595 		 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
5596 		 * @param {String} cls Class name to remove from each element.
5597 		 * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
5598 		 * were passed in.
5599 		 * @example
5600 		 * // Removes a class from all paragraphs in the active editor
5601 		 * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
5602 		 *
5603 		 * // Removes a class from a specific element in the current page
5604 		 * tinymce.DOM.removeClass('mydiv', 'myclass');
5605 		 */
5606 		removeClass: function(elm, cls) {
5607 			this.toggleClass(elm, cls, false);
5608 		},
5609 
5610 		/**
5611 		 * Returns true if the specified element has the specified class.
5612 		 *
5613 		 * @method hasClass
5614 		 * @param {String/Element} n HTML element or element id string to check CSS class on.
5615 		 * @param {String} c CSS class to check for.
5616 		 * @return {Boolean} true/false if the specified element has the specified class.
5617 		 */
5618 		hasClass: function(elm, cls) {
5619 			return this.$$(elm).hasClass(cls);
5620 		},
5621 
5622 		/**
5623 		 * Toggles the specified class on/off.
5624 		 *
5625 		 * @method toggleClass
5626 		 * @param {Element} elm Element to toggle class on.
5627 		 * @param {[type]} cls Class to toggle on/off.
5628 		 * @param {[type]} state Optional state to set.
5629 		 */
5630 		toggleClass: function(elm, cls, state) {
5631 			this.$$(elm).toggleClass(cls, state).each(function() {
5632 				if (this.className === '') {
5633 					$(this).attr('class', null);
5634 				}
5635 			});
5636 		},
5637 
5638 		/**
5639 		 * Shows the specified element(s) by ID by setting the "display" style.
5640 		 *
5641 		 * @method show
5642 		 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
5643 		 */
5644 		show: function(elm) {
5645 			this.$$(elm).show();
5646 		},
5647 
5648 		/**
5649 		 * Hides the specified element(s) by ID by setting the "display" style.
5650 		 *
5651 		 * @method hide
5652 		 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
5653 		 * @example
5654 		 * // Hides an element by id in the document
5655 		 * tinymce.DOM.hide('myid');
5656 		 */
5657 		hide: function(elm) {
5658 			this.$$(elm).hide();
5659 		},
5660 
5661 		/**
5662 		 * Returns true/false if the element is hidden or not by checking the "display" style.
5663 		 *
5664 		 * @method isHidden
5665 		 * @param {String/Element} e Id or element to check display state on.
5666 		 * @return {Boolean} true/false if the element is hidden or not.
5667 		 */
5668 		isHidden: function(elm) {
5669 			return this.$$(elm).css('display') == 'none';
5670 		},
5671 
5672 		/**
5673 		 * Returns a unique id. This can be useful when generating elements on the fly.
5674 		 * This method will not check if the element already exists.
5675 		 *
5676 		 * @method uniqueId
5677 		 * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
5678 		 * @return {String} Unique id.
5679 		 */
5680 		uniqueId: function(prefix) {
5681 			return (!prefix ? 'mce_' : prefix) + (this.counter++);
5682 		},
5683 
5684 		/**
5685 		 * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
5686 		 * URLs will get converted, hex color values fixed etc. Check processHTML for details.
5687 		 *
5688 		 * @method setHTML
5689 		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
5690 		 * @param {String} h HTML content to set as inner HTML of the element.
5691 		 * @example
5692 		 * // Sets the inner HTML of all paragraphs in the active editor
5693 		 * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
5694 		 *
5695 		 * // Sets the inner HTML of an element by id in the document
5696 		 * tinymce.DOM.setHTML('mydiv', 'some inner html');
5697 		 */
5698 		setHTML: function(elm, html) {
5699 			elm = this.$$(elm);
5700 
5701 			if (isIE) {
5702 				elm.each(function(i, target) {
5703 					if (target.canHaveHTML === false) {
5704 						return;
5705 					}
5706 
5707 					// Remove all child nodes, IE keeps empty text nodes in DOM
5708 					while (target.firstChild) {
5709 						target.removeChild(target.firstChild);
5710 					}
5711 
5712 					try {
5713 						// IE will remove comments from the beginning
5714 						// unless you padd the contents with something
5715 						target.innerHTML = '<br>' + html;
5716 						target.removeChild(target.firstChild);
5717 					} catch (ex) {
5718 						// IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
5719 						$('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
5720 					}
5721 
5722 					return html;
5723 				});
5724 			} else {
5725 				elm.html(html);
5726 			}
5727 		},
5728 
5729 		/**
5730 		 * Returns the outer HTML of an element.
5731 		 *
5732 		 * @method getOuterHTML
5733 		 * @param {String/Element} elm Element ID or element object to get outer HTML from.
5734 		 * @return {String} Outer HTML string.
5735 		 * @example
5736 		 * tinymce.DOM.getOuterHTML(editorElement);
5737 		 * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
5738 		 */
5739 		getOuterHTML: function(elm) {
5740 			elm = this.get(elm);
5741 			return elm.nodeType == 1 ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
5742 		},
5743 
5744 		/**
5745 		 * Sets the specified outer HTML on an element or elements.
5746 		 *
5747 		 * @method setOuterHTML
5748 		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
5749 		 * @param {Object} html HTML code to set as outer value for the element.
5750 		 * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
5751 		 * @example
5752 		 * // Sets the outer HTML of all paragraphs in the active editor
5753 		 * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
5754 		 *
5755 		 * // Sets the outer HTML of an element by id in the document
5756 		 * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
5757 		 */
5758 		setOuterHTML: function(elm, html) {
5759 			var self = this;
5760 
5761 			self.$$(elm).each(function() {
5762 				try {
5763 					this.outerHTML = html;
5764 				} catch (ex) {
5765 					// OuterHTML for IE it sometimes produces an "unknown runtime error"
5766 					self.remove($(this).html(html), true);
5767 				}
5768 			});
5769 		},
5770 
5771 		/**
5772 		 * Entity decodes a string. This method decodes any HTML entities, such as å.
5773 		 *
5774 		 * @method decode
5775 		 * @param {String} s String to decode entities on.
5776 		 * @return {String} Entity decoded string.
5777 		 */
5778 		decode: Entities.decode,
5779 
5780 		/**
5781 		 * Entity encodes a string. This method encodes the most common entities, such as <>"&.
5782 		 *
5783 		 * @method encode
5784 		 * @param {String} text String to encode with entities.
5785 		 * @return {String} Entity encoded string.
5786 		 */
5787 		encode: Entities.encodeAllRaw,
5788 
5789 		/**
5790 		 * Inserts an element after the reference element.
5791 		 *
5792 		 * @method insertAfter
5793 		 * @param {Element} node Element to insert after the reference.
5794 		 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
5795 		 * @return {Element/Array} Element that got added or an array with elements.
5796 		 */
5797 		insertAfter: function(node, referenceNode) {
5798 			referenceNode = this.get(referenceNode);
5799 
5800 			return this.run(node, function(node) {
5801 				var parent, nextSibling;
5802 
5803 				parent = referenceNode.parentNode;
5804 				nextSibling = referenceNode.nextSibling;
5805 
5806 				if (nextSibling) {
5807 					parent.insertBefore(node, nextSibling);
5808 				} else {
5809 					parent.appendChild(node);
5810 				}
5811 
5812 				return node;
5813 			});
5814 		},
5815 
5816 		/**
5817 		 * Replaces the specified element or elements with the new element specified. The new element will
5818 		 * be cloned if multiple input elements are passed in.
5819 		 *
5820 		 * @method replace
5821 		 * @param {Element} newElm New element to replace old ones with.
5822 		 * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
5823 		 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
5824 		 */
5825 		replace: function(newElm, oldElm, keepChildren) {
5826 			var self = this;
5827 
5828 			return self.run(oldElm, function(oldElm) {
5829 				if (is(oldElm, 'array')) {
5830 					newElm = newElm.cloneNode(true);
5831 				}
5832 
5833 				if (keepChildren) {
5834 					each(grep(oldElm.childNodes), function(node) {
5835 						newElm.appendChild(node);
5836 					});
5837 				}
5838 
5839 				return oldElm.parentNode.replaceChild(newElm, oldElm);
5840 			});
5841 		},
5842 
5843 		/**
5844 		 * Renames the specified element and keeps its attributes and children.
5845 		 *
5846 		 * @method rename
5847 		 * @param {Element} elm Element to rename.
5848 		 * @param {String} name Name of the new element.
5849 		 * @return {Element} New element or the old element if it needed renaming.
5850 		 */
5851 		rename: function(elm, name) {
5852 			var self = this, newElm;
5853 
5854 			if (elm.nodeName != name.toUpperCase()) {
5855 				// Rename block element
5856 				newElm = self.create(name);
5857 
5858 				// Copy attribs to new block
5859 				each(self.getAttribs(elm), function(attrNode) {
5860 					self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
5861 				});
5862 
5863 				// Replace block
5864 				self.replace(newElm, elm, 1);
5865 			}
5866 
5867 			return newElm || elm;
5868 		},
5869 
5870 		/**
5871 		 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
5872 		 *
5873 		 * @method findCommonAncestor
5874 		 * @param {Element} a Element to find common ancestor of.
5875 		 * @param {Element} b Element to find common ancestor of.
5876 		 * @return {Element} Common ancestor element of the two input elements.
5877 		 */
5878 		findCommonAncestor: function(a, b) {
5879 			var ps = a, pe;
5880 
5881 			while (ps) {
5882 				pe = b;
5883 
5884 				while (pe && ps != pe) {
5885 					pe = pe.parentNode;
5886 				}
5887 
5888 				if (ps == pe) {
5889 					break;
5890 				}
5891 
5892 				ps = ps.parentNode;
5893 			}
5894 
5895 			if (!ps && a.ownerDocument) {
5896 				return a.ownerDocument.documentElement;
5897 			}
5898 
5899 			return ps;
5900 		},
5901 
5902 		/**
5903 		 * Parses the specified RGB color value and returns a hex version of that color.
5904 		 *
5905 		 * @method toHex
5906 		 * @param {String} rgbVal RGB string value like rgb(1,2,3)
5907 		 * @return {String} Hex version of that RGB value like #FF00FF.
5908 		 */
5909 		toHex: function(rgbVal) {
5910 			return this.styles.toHex(Tools.trim(rgbVal));
5911 		},
5912 
5913 		/**
5914 		 * Executes the specified function on the element by id or dom element node or array of elements/id.
5915 		 *
5916 		 * @method run
5917 		 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
5918 		 * @param {function} f Function to execute for each item.
5919 		 * @param {Object} s Optional scope to execute the function in.
5920 		 * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
5921 		 */
5922 		run: function(elm, func, scope) {
5923 			var self = this, result;
5924 
5925 			if (typeof(elm) === 'string') {
5926 				elm = self.get(elm);
5927 			}
5928 
5929 			if (!elm) {
5930 				return false;
5931 			}
5932 
5933 			scope = scope || this;
5934 			if (!elm.nodeType && (elm.length || elm.length === 0)) {
5935 				result = [];
5936 
5937 				each(elm, function(elm, i) {
5938 					if (elm) {
5939 						if (typeof(elm) == 'string') {
5940 							elm = self.get(elm);
5941 						}
5942 
5943 						result.push(func.call(scope, elm, i));
5944 					}
5945 				});
5946 
5947 				return result;
5948 			}
5949 
5950 			return func.call(scope, elm);
5951 		},
5952 
5953 		/**
5954 		 * Returns a NodeList with attributes for the element.
5955 		 *
5956 		 * @method getAttribs
5957 		 * @param {HTMLElement/string} elm Element node or string id to get attributes from.
5958 		 * @return {NodeList} NodeList with attributes.
5959 		 */
5960 		getAttribs: function(elm) {
5961 			var attrs;
5962 
5963 			elm = this.get(elm);
5964 
5965 			if (!elm) {
5966 				return [];
5967 			}
5968 
5969 			if (isIE) {
5970 				attrs = [];
5971 
5972 				// Object will throw exception in IE
5973 				if (elm.nodeName == 'OBJECT') {
5974 					return elm.attributes;
5975 				}
5976 
5977 				// IE doesn't keep the selected attribute if you clone option elements
5978 				if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
5979 					attrs.push({specified: 1, nodeName: 'selected'});
5980 				}
5981 
5982 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
5983 				var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
5984 				elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
5985 					attrs.push({specified: 1, nodeName: a});
5986 				});
5987 
5988 				return attrs;
5989 			}
5990 
5991 			return elm.attributes;
5992 		},
5993 
5994 		/**
5995 		 * Returns true/false if the specified node is to be considered empty or not.
5996 		 *
5997 		 * @example
5998 		 * tinymce.DOM.isEmpty(node, {img: true});
5999 		 * @method isEmpty
6000 		 * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
6001 		 * @return {Boolean} true/false if the node is empty or not.
6002 		 */
6003 		isEmpty: function(node, elements) {
6004 			var self = this, i, attributes, type, walker, name, brCount = 0;
6005 
6006 			node = node.firstChild;
6007 			if (node) {
6008 				walker = new TreeWalker(node, node.parentNode);
6009 				elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null);
6010 
6011 				do {
6012 					type = node.nodeType;
6013 
6014 					if (type === 1) {
6015 						// Ignore bogus elements
6016 						if (node.getAttribute('data-mce-bogus')) {
6017 							continue;
6018 						}
6019 
6020 						// Keep empty elements like <img />
6021 						name = node.nodeName.toLowerCase();
6022 						if (elements && elements[name]) {
6023 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6024 							if (name === 'br') {
6025 								brCount++;
6026 								continue;
6027 							}
6028 
6029 							return false;
6030 						}
6031 
6032 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6033 						attributes = self.getAttribs(node);
6034 						i = attributes.length;
6035 						while (i--) {
6036 							name = attributes[i].nodeName;
6037 							if (name === "name" || name === 'data-mce-bookmark') {
6038 								return false;
6039 							}
6040 						}
6041 					}
6042 
6043 					// Keep comment nodes
6044 					if (type == 8) {
6045 						return false;
6046 					}
6047 
6048 					// Keep non whitespace text nodes
6049 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
6050 						return false;
6051 					}
6052 				} while ((node = walker.next()));
6053 			}
6054 
6055 			return brCount <= 1;
6056 		},
6057 
6058 		/**
6059 		 * Creates a new DOM Range object. This will use the native DOM Range API if it's
6060 		 * available. If it's not, it will fall back to the custom TinyMCE implementation.
6061 		 *
6062 		 * @method createRng
6063 		 * @return {DOMRange} DOM Range object.
6064 		 * @example
6065 		 * var rng = tinymce.DOM.createRng();
6066 		 * alert(rng.startContainer + "," + rng.startOffset);
6067 		 */
6068 		createRng: function() {
6069 			var doc = this.doc;
6070 
6071 			return doc.createRange ? doc.createRange() : new Range(this);
6072 		},
6073 
6074 		/**
6075 		 * Returns the index of the specified node within its parent.
6076 		 *
6077 		 * @method nodeIndex
6078 		 * @param {Node} node Node to look for.
6079 		 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
6080 		 * @return {Number} Index of the specified node.
6081 		 */
6082 		nodeIndex: function(node, normalized) {
6083 			var idx = 0, lastNodeType, nodeType;
6084 
6085 			if (node) {
6086 				for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
6087 					nodeType = node.nodeType;
6088 
6089 					// Normalize text nodes
6090 					if (normalized && nodeType == 3) {
6091 						if (nodeType == lastNodeType || !node.nodeValue.length) {
6092 							continue;
6093 						}
6094 					}
6095 					idx++;
6096 					lastNodeType = nodeType;
6097 				}
6098 			}
6099 
6100 			return idx;
6101 		},
6102 
6103 		/**
6104 		 * Splits an element into two new elements and places the specified split
6105 		 * element or elements between the new ones. For example splitting the paragraph at the bold element in
6106 		 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
6107 		 *
6108 		 * @method split
6109 		 * @param {Element} parentElm Parent element to split.
6110 		 * @param {Element} splitElm Element to split at.
6111 		 * @param {Element} replacementElm Optional replacement element to replace the split element with.
6112 		 * @return {Element} Returns the split element or the replacement element if that is specified.
6113 		 */
6114 		split: function(parentElm, splitElm, replacementElm) {
6115 			var self = this, r = self.createRng(), bef, aft, pa;
6116 
6117 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
6118 			// but we don't want that in our code since it serves no purpose for the end user
6119 			// For example splitting this html at the bold element:
6120 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6121 			// would produce:
6122 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6123 			// this function will then trim off empty edges and produce:
6124 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6125 			function trimNode(node) {
6126 				var i, children = node.childNodes, type = node.nodeType;
6127 
6128 				function surroundedBySpans(node) {
6129 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6130 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6131 					return previousIsSpan && nextIsSpan;
6132 				}
6133 
6134 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
6135 					return;
6136 				}
6137 
6138 				for (i = children.length - 1; i >= 0; i--) {
6139 					trimNode(children[i]);
6140 				}
6141 
6142 				if (type != 9) {
6143 					// Keep non whitespace text nodes
6144 					if (type == 3 && node.nodeValue.length > 0) {
6145 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6146 						// Also keep text nodes with only spaces if surrounded by spans.
6147 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6148 						var trimmedLength = trim(node.nodeValue).length;
6149 						if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
6150 							return;
6151 						}
6152 					} else if (type == 1) {
6153 						// If the only child is a bookmark then move it up
6154 						children = node.childNodes;
6155 
6156 						// TODO fix this complex if
6157 						if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
6158 							children[0].getAttribute('data-mce-type') == 'bookmark') {
6159 							node.parentNode.insertBefore(children[0], node);
6160 						}
6161 
6162 						// Keep non empty elements or img, hr etc
6163 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
6164 							return;
6165 						}
6166 					}
6167 
6168 					self.remove(node);
6169 				}
6170 
6171 				return node;
6172 			}
6173 
6174 			if (parentElm && splitElm) {
6175 				// Get before chunk
6176 				r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
6177 				r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
6178 				bef = r.extractContents();
6179 
6180 				// Get after chunk
6181 				r = self.createRng();
6182 				r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
6183 				r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
6184 				aft = r.extractContents();
6185 
6186 				// Insert before chunk
6187 				pa = parentElm.parentNode;
6188 				pa.insertBefore(trimNode(bef), parentElm);
6189 
6190 				// Insert middle chunk
6191 				if (replacementElm) {
6192 					pa.replaceChild(replacementElm, splitElm);
6193 				} else {
6194 					pa.insertBefore(splitElm, parentElm);
6195 				}
6196 
6197 				// Insert after chunk
6198 				pa.insertBefore(trimNode(aft), parentElm);
6199 				self.remove(parentElm);
6200 
6201 				return replacementElm || splitElm;
6202 			}
6203 		},
6204 
6205 		/**
6206 		 * Adds an event handler to the specified object.
6207 		 *
6208 		 * @method bind
6209 		 * @param {Element/Document/Window/Array} target Target element to bind events to.
6210 		 * handler to or an array of elements/ids/documents.
6211 		 * @param {String} name Name of event handler to add, for example: click.
6212 		 * @param {function} func Function to execute when the event occurs.
6213 		 * @param {Object} scope Optional scope to execute the function in.
6214 		 * @return {function} Function callback handler the same as the one passed in.
6215 		 */
6216 		bind: function(target, name, func, scope) {
6217 			var self = this;
6218 
6219 			if (Tools.isArray(target)) {
6220 				var i = target.length;
6221 
6222 				while (i--) {
6223 					target[i] = self.bind(target[i], name, func, scope);
6224 				}
6225 
6226 				return target;
6227 			}
6228 
6229 			// Collect all window/document events bound by editor instance
6230 			if (self.settings.collect && (target === self.doc || target === self.win)) {
6231 				self.boundEvents.push([target, name, func, scope]);
6232 			}
6233 
6234 			return self.events.bind(target, name, func, scope || self);
6235 		},
6236 
6237 		/**
6238 		 * Removes the specified event handler by name and function from an element or collection of elements.
6239 		 *
6240 		 * @method unbind
6241 		 * @param {Element/Document/Window/Array} target Target element to unbind events on.
6242 		 * @param {String} name Event handler name, for example: "click"
6243 		 * @param {function} func Function to remove.
6244 		 * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
6245 		 * were passed in.
6246 		 */
6247 		unbind: function(target, name, func) {
6248 			var self = this, i;
6249 
6250 			if (Tools.isArray(target)) {
6251 				i = target.length;
6252 
6253 				while (i--) {
6254 					target[i] = self.unbind(target[i], name, func);
6255 				}
6256 
6257 				return target;
6258 			}
6259 
6260 			// Remove any bound events matching the input
6261 			if (self.boundEvents && (target === self.doc || target === self.win)) {
6262 				i = self.boundEvents.length;
6263 
6264 				while (i--) {
6265 					var item = self.boundEvents[i];
6266 
6267 					if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
6268 						this.events.unbind(item[0], item[1], item[2]);
6269 					}
6270 				}
6271 			}
6272 
6273 			return this.events.unbind(target, name, func);
6274 		},
6275 
6276 		/**
6277 		 * Fires the specified event name with object on target.
6278 		 *
6279 		 * @method fire
6280 		 * @param {Node/Document/Window} target Target element or object to fire event on.
6281 		 * @param {String} name Name of the event to fire.
6282 		 * @param {Object} evt Event object to send.
6283 		 * @return {Event} Event object.
6284 		 */
6285 		fire: function(target, name, evt) {
6286 			return this.events.fire(target, name, evt);
6287 		},
6288 
6289 		// Returns the content editable state of a node
6290 		getContentEditable: function(node) {
6291 			var contentEditable;
6292 
6293 			// Check type
6294 			if (!node || node.nodeType != 1) {
6295 				return null;
6296 			}
6297 
6298 			// Check for fake content editable
6299 			contentEditable = node.getAttribute("data-mce-contenteditable");
6300 			if (contentEditable && contentEditable !== "inherit") {
6301 				return contentEditable;
6302 			}
6303 
6304 			// Check for real content editable
6305 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6306 		},
6307 
6308 		getContentEditableParent: function(node) {
6309 			var root = this.getRoot(), state = null;
6310 
6311 			for (; node && node !== root; node = node.parentNode) {
6312 				state = this.getContentEditable(node);
6313 
6314 				if (state !== null) {
6315 					break;
6316 				}
6317 			}
6318 
6319 			return state;
6320 		},
6321 
6322 		/**
6323 		 * Destroys all internal references to the DOM to solve IE leak issues.
6324 		 *
6325 		 * @method destroy
6326 		 */
6327 		destroy: function() {
6328 			var self = this;
6329 
6330 			// Unbind all events bound to window/document by editor instance
6331 			if (self.boundEvents) {
6332 				var i = self.boundEvents.length;
6333 
6334 				while (i--) {
6335 					var item = self.boundEvents[i];
6336 					this.events.unbind(item[0], item[1], item[2]);
6337 				}
6338 
6339 				self.boundEvents = null;
6340 			}
6341 
6342 			// Restore sizzle document to window.document
6343 			// Since the current document might be removed producing "Permission denied" on IE see #6325
6344 			if (Sizzle.setDocument) {
6345 				Sizzle.setDocument();
6346 			}
6347 
6348 			self.win = self.doc = self.root = self.events = self.frag = null;
6349 		},
6350 
6351 		isChildOf: function(node, parent) {
6352 			while (node) {
6353 				if (parent === node) {
6354 					return true;
6355 				}
6356 
6357 				node = node.parentNode;
6358 			}
6359 
6360 			return false;
6361 		},
6362 
6363 		// #ifdef debug
6364 
6365 		dumpRng: function(r) {
6366 			return (
6367 				'startContainer: ' + r.startContainer.nodeName +
6368 				', startOffset: ' + r.startOffset +
6369 				', endContainer: ' + r.endContainer.nodeName +
6370 				', endOffset: ' + r.endOffset
6371 			);
6372 		},
6373 
6374 		// #endif
6375 
6376 		_findSib: function(node, selector, name) {
6377 			var self = this, func = selector;
6378 
6379 			if (node) {
6380 				// If expression make a function of it using is
6381 				if (typeof(func) == 'string') {
6382 					func = function(node) {
6383 						return self.is(node, selector);
6384 					};
6385 				}
6386 
6387 				// Loop all siblings
6388 				for (node = node[name]; node; node = node[name]) {
6389 					if (func(node)) {
6390 						return node;
6391 					}
6392 				}
6393 			}
6394 
6395 			return null;
6396 		}
6397 	};
6398 
6399 	/**
6400 	 * Instance of DOMUtils for the current document.
6401 	 *
6402 	 * @static
6403 	 * @property DOM
6404 	 * @type tinymce.dom.DOMUtils
6405 	 * @example
6406 	 * // Example of how to add a class to some element by id
6407 	 * tinymce.DOM.addClass('someid', 'someclass');
6408 	 */
6409 	DOMUtils.DOM = new DOMUtils(document);
6410 
6411 	return DOMUtils;
6412 });
6413 
6414 // Included from: js/tinymce/classes/dom/ScriptLoader.js
6415 
6416 /**
6417  * ScriptLoader.js
6418  *
6419  * Copyright, Moxiecode Systems AB
6420  * Released under LGPL License.
6421  *
6422  * License: http://www.tinymce.com/license
6423  * Contributing: http://www.tinymce.com/contributing
6424  */
6425 
6426 /*globals console*/
6427 
6428 /**
6429  * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
6430  * when various items gets loaded. This class is useful to load external JavaScript files.
6431  *
6432  * @class tinymce.dom.ScriptLoader
6433  * @example
6434  * // Load a script from a specific URL using the global script loader
6435  * tinymce.ScriptLoader.load('somescript.js');
6436  *
6437  * // Load a script using a unique instance of the script loader
6438  * var scriptLoader = new tinymce.dom.ScriptLoader();
6439  *
6440  * scriptLoader.load('somescript.js');
6441  *
6442  * // Load multiple scripts
6443  * var scriptLoader = new tinymce.dom.ScriptLoader();
6444  *
6445  * scriptLoader.add('somescript1.js');
6446  * scriptLoader.add('somescript2.js');
6447  * scriptLoader.add('somescript3.js');
6448  *
6449  * scriptLoader.loadQueue(function() {
6450  *    alert('All scripts are now loaded.');
6451  * });
6452  */
6453 define("tinymce/dom/ScriptLoader", [
6454 	"tinymce/dom/DOMUtils",
6455 	"tinymce/util/Tools"
6456 ], function(DOMUtils, Tools) {
6457 	var DOM = DOMUtils.DOM;
6458 	var each = Tools.each, grep = Tools.grep;
6459 
6460 	function ScriptLoader() {
6461 		var QUEUED = 0,
6462 			LOADING = 1,
6463 			LOADED = 2,
6464 			states = {},
6465 			queue = [],
6466 			scriptLoadedCallbacks = {},
6467 			queueLoadedCallbacks = [],
6468 			loading = 0,
6469 			undef;
6470 
6471 		/**
6472 		 * Loads a specific script directly without adding it to the load queue.
6473 		 *
6474 		 * @method load
6475 		 * @param {String} url Absolute URL to script to add.
6476 		 * @param {function} callback Optional callback function to execute ones this script gets loaded.
6477 		 * @param {Object} scope Optional scope to execute callback in.
6478 		 */
6479 		function loadScript(url, callback) {
6480 			var dom = DOM, elm, id;
6481 
6482 			// Execute callback when script is loaded
6483 			function done() {
6484 				dom.remove(id);
6485 
6486 				if (elm) {
6487 					elm.onreadystatechange = elm.onload = elm = null;
6488 				}
6489 
6490 				callback();
6491 			}
6492 
6493 			function error() {
6494 				/*eslint no-console:0 */
6495 
6496 				// Report the error so it's easier for people to spot loading errors
6497 				if (typeof(console) !== "undefined" && console.log) {
6498 					console.log("Failed to load: " + url);
6499 				}
6500 
6501 				// We can't mark it as done if there is a load error since
6502 				// A) We don't want to produce 404 errors on the server and
6503 				// B) the onerror event won't fire on all browsers.
6504 				// done();
6505 			}
6506 
6507 			id = dom.uniqueId();
6508 
6509 			// Create new script element
6510 			elm = document.createElement('script');
6511 			elm.id = id;
6512 			elm.type = 'text/javascript';
6513 			elm.src = url;
6514 
6515 			// Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
6516 			if ("onreadystatechange" in elm) {
6517 				elm.onreadystatechange = function() {
6518 					if (/loaded|complete/.test(elm.readyState)) {
6519 						done();
6520 					}
6521 				};
6522 			} else {
6523 				elm.onload = done;
6524 			}
6525 
6526 			// Add onerror event will get fired on some browsers but not all of them
6527 			elm.onerror = error;
6528 
6529 			// Add script to document
6530 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
6531 		}
6532 
6533 		/**
6534 		 * Returns true/false if a script has been loaded or not.
6535 		 *
6536 		 * @method isDone
6537 		 * @param {String} url URL to check for.
6538 		 * @return {Boolean} true/false if the URL is loaded.
6539 		 */
6540 		this.isDone = function(url) {
6541 			return states[url] == LOADED;
6542 		};
6543 
6544 		/**
6545 		 * Marks a specific script to be loaded. This can be useful if a script got loaded outside
6546 		 * the script loader or to skip it from loading some script.
6547 		 *
6548 		 * @method markDone
6549 		 * @param {string} u Absolute URL to the script to mark as loaded.
6550 		 */
6551 		this.markDone = function(url) {
6552 			states[url] = LOADED;
6553 		};
6554 
6555 		/**
6556 		 * Adds a specific script to the load queue of the script loader.
6557 		 *
6558 		 * @method add
6559 		 * @param {String} url Absolute URL to script to add.
6560 		 * @param {function} callback Optional callback function to execute ones this script gets loaded.
6561 		 * @param {Object} scope Optional scope to execute callback in.
6562 		 */
6563 		this.add = this.load = function(url, callback, scope) {
6564 			var state = states[url];
6565 
6566 			// Add url to load queue
6567 			if (state == undef) {
6568 				queue.push(url);
6569 				states[url] = QUEUED;
6570 			}
6571 
6572 			if (callback) {
6573 				// Store away callback for later execution
6574 				if (!scriptLoadedCallbacks[url]) {
6575 					scriptLoadedCallbacks[url] = [];
6576 				}
6577 
6578 				scriptLoadedCallbacks[url].push({
6579 					func: callback,
6580 					scope: scope || this
6581 				});
6582 			}
6583 		};
6584 
6585 		/**
6586 		 * Starts the loading of the queue.
6587 		 *
6588 		 * @method loadQueue
6589 		 * @param {function} callback Optional callback to execute when all queued items are loaded.
6590 		 * @param {Object} scope Optional scope to execute the callback in.
6591 		 */
6592 		this.loadQueue = function(callback, scope) {
6593 			this.loadScripts(queue, callback, scope);
6594 		};
6595 
6596 		/**
6597 		 * Loads the specified queue of files and executes the callback ones they are loaded.
6598 		 * This method is generally not used outside this class but it might be useful in some scenarios.
6599 		 *
6600 		 * @method loadScripts
6601 		 * @param {Array} scripts Array of queue items to load.
6602 		 * @param {function} callback Optional callback to execute ones all items are loaded.
6603 		 * @param {Object} scope Optional scope to execute callback in.
6604 		 */
6605 		this.loadScripts = function(scripts, callback, scope) {
6606 			var loadScripts;
6607 
6608 			function execScriptLoadedCallbacks(url) {
6609 				// Execute URL callback functions
6610 				each(scriptLoadedCallbacks[url], function(callback) {
6611 					callback.func.call(callback.scope);
6612 				});
6613 
6614 				scriptLoadedCallbacks[url] = undef;
6615 			}
6616 
6617 			queueLoadedCallbacks.push({
6618 				func: callback,
6619 				scope: scope || this
6620 			});
6621 
6622 			loadScripts = function() {
6623 				var loadingScripts = grep(scripts);
6624 
6625 				// Current scripts has been handled
6626 				scripts.length = 0;
6627 
6628 				// Load scripts that needs to be loaded
6629 				each(loadingScripts, function(url) {
6630 					// Script is already loaded then execute script callbacks directly
6631 					if (states[url] == LOADED) {
6632 						execScriptLoadedCallbacks(url);
6633 						return;
6634 					}
6635 
6636 					// Is script not loading then start loading it
6637 					if (states[url] != LOADING) {
6638 						states[url] = LOADING;
6639 						loading++;
6640 
6641 						loadScript(url, function() {
6642 							states[url] = LOADED;
6643 							loading--;
6644 
6645 							execScriptLoadedCallbacks(url);
6646 
6647 							// Load more scripts if they where added by the recently loaded script
6648 							loadScripts();
6649 						});
6650 					}
6651 				});
6652 
6653 				// No scripts are currently loading then execute all pending queue loaded callbacks
6654 				if (!loading) {
6655 					each(queueLoadedCallbacks, function(callback) {
6656 						callback.func.call(callback.scope);
6657 					});
6658 
6659 					queueLoadedCallbacks.length = 0;
6660 				}
6661 			};
6662 
6663 			loadScripts();
6664 		};
6665 	}
6666 
6667 	ScriptLoader.ScriptLoader = new ScriptLoader();
6668 
6669 	return ScriptLoader;
6670 });
6671 
6672 // Included from: js/tinymce/classes/AddOnManager.js
6673 
6674 /**
6675  * AddOnManager.js
6676  *
6677  * Copyright, Moxiecode Systems AB
6678  * Released under LGPL License.
6679  *
6680  * License: http://www.tinymce.com/license
6681  * Contributing: http://www.tinymce.com/contributing
6682  */
6683 
6684 /**
6685  * This class handles the loading of themes/plugins or other add-ons and their language packs.
6686  *
6687  * @class tinymce.AddOnManager
6688  */
6689 define("tinymce/AddOnManager", [
6690 	"tinymce/dom/ScriptLoader",
6691 	"tinymce/util/Tools"
6692 ], function(ScriptLoader, Tools) {
6693 	var each = Tools.each;
6694 
6695 	function AddOnManager() {
6696 		var self = this;
6697 
6698 		self.items = [];
6699 		self.urls = {};
6700 		self.lookup = {};
6701 	}
6702 
6703 	AddOnManager.prototype = {
6704 		/**
6705 		 * Returns the specified add on by the short name.
6706 		 *
6707 		 * @method get
6708 		 * @param {String} name Add-on to look for.
6709 		 * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
6710 		 */
6711 		get: function(name) {
6712 			if (this.lookup[name]) {
6713 				return this.lookup[name].instance;
6714 			} else {
6715 				return undefined;
6716 			}
6717 		},
6718 
6719 		dependencies: function(name) {
6720 			var result;
6721 
6722 			if (this.lookup[name]) {
6723 				result = this.lookup[name].dependencies;
6724 			}
6725 
6726 			return result || [];
6727 		},
6728 
6729 		/**
6730 		 * Loads a language pack for the specified add-on.
6731 		 *
6732 		 * @method requireLangPack
6733 		 * @param {String} name Short name of the add-on.
6734 		 * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
6735 		 */
6736 		requireLangPack: function(name, languages) {
6737 			var language = AddOnManager.language;
6738 
6739 			if (language && AddOnManager.languageLoad !== false) {
6740 				if (languages) {
6741 					languages = ',' + languages + ',';
6742 
6743 					// Load short form sv.js or long form sv_SE.js
6744 					if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
6745 						language = language.substr(0, 2);
6746 					} else if (languages.indexOf(',' + language + ',') == -1) {
6747 						return;
6748 					}
6749 				}
6750 
6751 				ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
6752 			}
6753 		},
6754 
6755 		/**
6756 		 * Adds a instance of the add-on by it's short name.
6757 		 *
6758 		 * @method add
6759 		 * @param {String} id Short name/id for the add-on.
6760 		 * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
6761 		 * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
6762 		 * @example
6763 		 * // Create a simple plugin
6764 		 * tinymce.create('tinymce.plugins.TestPlugin', {
6765 		 *   TestPlugin: function(ed, url) {
6766 		 *   ed.on('click', function(e) {
6767 		 *      ed.windowManager.alert('Hello World!');
6768 		 *   });
6769 		 *   }
6770 		 * });
6771 		 *
6772 		 * // Register plugin using the add method
6773 		 * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
6774 		 *
6775 		 * // Initialize TinyMCE
6776 		 * tinymce.init({
6777 		 *  ...
6778 		 *  plugins: '-test' // Init the plugin but don't try to load it
6779 		 * });
6780 		 */
6781 		add: function(id, addOn, dependencies) {
6782 			this.items.push(addOn);
6783 			this.lookup[id] = {instance: addOn, dependencies: dependencies};
6784 
6785 			return addOn;
6786 		},
6787 
6788 		createUrl: function(baseUrl, dep) {
6789 			if (typeof dep === "object") {
6790 				return dep;
6791 			} else {
6792 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
6793 			}
6794 		},
6795 
6796 		/**
6797 		 * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
6798 		 * This should be used in development mode.  A new compressor/javascript munger process will ensure that the
6799 		 * components are put together into the plugin.js file and compressed correctly.
6800 		 *
6801 		 * @method addComponents
6802 		 * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
6803 		 * @param {Array} scripts Array containing the names of the scripts to load.
6804 		 */
6805 		addComponents: function(pluginName, scripts) {
6806 			var pluginUrl = this.urls[pluginName];
6807 
6808 			each(scripts, function(script) {
6809 				ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
6810 			});
6811 		},
6812 
6813 		/**
6814 		 * Loads an add-on from a specific url.
6815 		 *
6816 		 * @method load
6817 		 * @param {String} name Short name of the add-on that gets loaded.
6818 		 * @param {String} addOnUrl URL to the add-on that will get loaded.
6819 		 * @param {function} callback Optional callback to execute ones the add-on is loaded.
6820 		 * @param {Object} scope Optional scope to execute the callback in.
6821 		 * @example
6822 		 * // Loads a plugin from an external URL
6823 		 * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
6824 		 *
6825 		 * // Initialize TinyMCE
6826 		 * tinymce.init({
6827 		 *  ...
6828 		 *  plugins: '-myplugin' // Don't try to load it again
6829 		 * });
6830 		 */
6831 		load: function(name, addOnUrl, callback, scope) {
6832 			var self = this, url = addOnUrl;
6833 
6834 			function loadDependencies() {
6835 				var dependencies = self.dependencies(name);
6836 
6837 				each(dependencies, function(dep) {
6838 					var newUrl = self.createUrl(addOnUrl, dep);
6839 
6840 					self.load(newUrl.resource, newUrl, undefined, undefined);
6841 				});
6842 
6843 				if (callback) {
6844 					if (scope) {
6845 						callback.call(scope);
6846 					} else {
6847 						callback.call(ScriptLoader);
6848 					}
6849 				}
6850 			}
6851 
6852 			if (self.urls[name]) {
6853 				return;
6854 			}
6855 
6856 			if (typeof addOnUrl === "object") {
6857 				url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
6858 			}
6859 
6860 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
6861 				url = AddOnManager.baseURL + '/' + url;
6862 			}
6863 
6864 			self.urls[name] = url.substring(0, url.lastIndexOf('/'));
6865 
6866 			if (self.lookup[name]) {
6867 				loadDependencies();
6868 			} else {
6869 				ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
6870 			}
6871 		}
6872 	};
6873 
6874 	AddOnManager.PluginManager = new AddOnManager();
6875 	AddOnManager.ThemeManager = new AddOnManager();
6876 
6877 	return AddOnManager;
6878 });
6879 
6880 /**
6881  * TinyMCE theme class.
6882  *
6883  * @class tinymce.Theme
6884  */
6885 
6886 /**
6887  * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
6888  *
6889  * @method renderUI
6890  * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
6891  * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
6892  */
6893 
6894 /**
6895  * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional.
6896  *
6897  * @class tinymce.Plugin
6898  * @example
6899  * tinymce.PluginManager.add('example', function(editor, url) {
6900  *     // Add a button that opens a window
6901  *     editor.addButton('example', {
6902  *         text: 'My button',
6903  *         icon: false,
6904  *         onclick: function() {
6905  *             // Open window
6906  *             editor.windowManager.open({
6907  *                 title: 'Example plugin',
6908  *                 body: [
6909  *                     {type: 'textbox', name: 'title', label: 'Title'}
6910  *                 ],
6911  *                 onsubmit: function(e) {
6912  *                     // Insert content when the window form is submitted
6913  *                     editor.insertContent('Title: ' + e.data.title);
6914  *                 }
6915  *             });
6916  *         }
6917  *     });
6918  *
6919  *     // Adds a menu item to the tools menu
6920  *     editor.addMenuItem('example', {
6921  *         text: 'Example plugin',
6922  *         context: 'tools',
6923  *         onclick: function() {
6924  *             // Open window with a specific url
6925  *             editor.windowManager.open({
6926  *                 title: 'TinyMCE site',
6927  *                 url: 'http://www.tinymce.com',
6928  *                 width: 800,
6929  *                 height: 600,
6930  *                 buttons: [{
6931  *                     text: 'Close',
6932  *                     onclick: 'close'
6933  *                 }]
6934  *             });
6935  *         }
6936  *     });
6937  * });
6938  */
6939 
6940 // Included from: js/tinymce/classes/dom/RangeUtils.js
6941 
6942 /**
6943  * RangeUtils.js
6944  *
6945  * Copyright, Moxiecode Systems AB
6946  * Released under LGPL License.
6947  *
6948  * License: http://www.tinymce.com/license
6949  * Contributing: http://www.tinymce.com/contributing
6950  */
6951 
6952 /**
6953  * This class contains a few utility methods for ranges.
6954  *
6955  * @class tinymce.dom.RangeUtils
6956  * @private
6957  */
6958 define("tinymce/dom/RangeUtils", [
6959 	"tinymce/util/Tools",
6960 	"tinymce/dom/TreeWalker"
6961 ], function(Tools, TreeWalker) {
6962 	var each = Tools.each;
6963 
6964 	function getEndChild(container, index) {
6965 		var childNodes = container.childNodes;
6966 
6967 		index--;
6968 
6969 		if (index > childNodes.length - 1) {
6970 			index = childNodes.length - 1;
6971 		} else if (index < 0) {
6972 			index = 0;
6973 		}
6974 
6975 		return childNodes[index] || container;
6976 	}
6977 
6978 	function RangeUtils(dom) {
6979 		/**
6980 		 * Walks the specified range like object and executes the callback for each sibling collection it finds.
6981 		 *
6982 		 * @method walk
6983 		 * @param {Object} rng Range like object.
6984 		 * @param {function} callback Callback function to execute for each sibling collection.
6985 		 */
6986 		this.walk = function(rng, callback) {
6987 			var startContainer = rng.startContainer,
6988 				startOffset = rng.startOffset,
6989 				endContainer = rng.endContainer,
6990 				endOffset = rng.endOffset,
6991 				ancestor, startPoint,
6992 				endPoint, node, parent, siblings, nodes;
6993 
6994 			// Handle table cell selection the table plugin enables
6995 			// you to fake select table cells and perform formatting actions on them
6996 			nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
6997 			if (nodes.length > 0) {
6998 				each(nodes, function(node) {
6999 					callback([node]);
7000 				});
7001 
7002 				return;
7003 			}
7004 
7005 			/**
7006 			 * Excludes start/end text node if they are out side the range
7007 			 *
7008 			 * @private
7009 			 * @param {Array} nodes Nodes to exclude items from.
7010 			 * @return {Array} Array with nodes excluding the start/end container if needed.
7011 			 */
7012 			function exclude(nodes) {
7013 				var node;
7014 
7015 				// First node is excluded
7016 				node = nodes[0];
7017 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
7018 					nodes.splice(0, 1);
7019 				}
7020 
7021 				// Last node is excluded
7022 				node = nodes[nodes.length - 1];
7023 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
7024 					nodes.splice(nodes.length - 1, 1);
7025 				}
7026 
7027 				return nodes;
7028 			}
7029 
7030 			/**
7031 			 * Collects siblings
7032 			 *
7033 			 * @private
7034 			 * @param {Node} node Node to collect siblings from.
7035 			 * @param {String} name Name of the sibling to check for.
7036 			 * @return {Array} Array of collected siblings.
7037 			 */
7038 			function collectSiblings(node, name, end_node) {
7039 				var siblings = [];
7040 
7041 				for (; node && node != end_node; node = node[name]) {
7042 					siblings.push(node);
7043 				}
7044 
7045 				return siblings;
7046 			}
7047 
7048 			/**
7049 			 * Find an end point this is the node just before the common ancestor root.
7050 			 *
7051 			 * @private
7052 			 * @param {Node} node Node to start at.
7053 			 * @param {Node} root Root/ancestor element to stop just before.
7054 			 * @return {Node} Node just before the root element.
7055 			 */
7056 			function findEndPoint(node, root) {
7057 				do {
7058 					if (node.parentNode == root) {
7059 						return node;
7060 					}
7061 
7062 					node = node.parentNode;
7063 				} while (node);
7064 			}
7065 
7066 			function walkBoundary(start_node, end_node, next) {
7067 				var siblingName = next ? 'nextSibling' : 'previousSibling';
7068 
7069 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7070 					parent = node.parentNode;
7071 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7072 
7073 					if (siblings.length) {
7074 						if (!next) {
7075 							siblings.reverse();
7076 						}
7077 
7078 						callback(exclude(siblings));
7079 					}
7080 				}
7081 			}
7082 
7083 			// If index based start position then resolve it
7084 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
7085 				startContainer = startContainer.childNodes[startOffset];
7086 			}
7087 
7088 			// If index based end position then resolve it
7089 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
7090 				endContainer = getEndChild(endContainer, endOffset);
7091 			}
7092 
7093 			// Same container
7094 			if (startContainer == endContainer) {
7095 				return callback(exclude([startContainer]));
7096 			}
7097 
7098 			// Find common ancestor and end points
7099 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
7100 
7101 			// Process left side
7102 			for (node = startContainer; node; node = node.parentNode) {
7103 				if (node === endContainer) {
7104 					return walkBoundary(startContainer, ancestor, true);
7105 				}
7106 
7107 				if (node === ancestor) {
7108 					break;
7109 				}
7110 			}
7111 
7112 			// Process right side
7113 			for (node = endContainer; node; node = node.parentNode) {
7114 				if (node === startContainer) {
7115 					return walkBoundary(endContainer, ancestor);
7116 				}
7117 
7118 				if (node === ancestor) {
7119 					break;
7120 				}
7121 			}
7122 
7123 			// Find start/end point
7124 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
7125 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
7126 
7127 			// Walk left leaf
7128 			walkBoundary(startContainer, startPoint, true);
7129 
7130 			// Walk the middle from start to end point
7131 			siblings = collectSiblings(
7132 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
7133 				'nextSibling',
7134 				endPoint == endContainer ? endPoint.nextSibling : endPoint
7135 			);
7136 
7137 			if (siblings.length) {
7138 				callback(exclude(siblings));
7139 			}
7140 
7141 			// Walk right leaf
7142 			walkBoundary(endContainer, endPoint);
7143 		};
7144 
7145 		/**
7146 		 * Splits the specified range at it's start/end points.
7147 		 *
7148 		 * @private
7149 		 * @param {Range/RangeObject} rng Range to split.
7150 		 * @return {Object} Range position object.
7151 		 */
7152 		this.split = function(rng) {
7153 			var startContainer = rng.startContainer,
7154 				startOffset = rng.startOffset,
7155 				endContainer = rng.endContainer,
7156 				endOffset = rng.endOffset;
7157 
7158 			function splitText(node, offset) {
7159 				return node.splitText(offset);
7160 			}
7161 
7162 			// Handle single text node
7163 			if (startContainer == endContainer && startContainer.nodeType == 3) {
7164 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
7165 					endContainer = splitText(startContainer, startOffset);
7166 					startContainer = endContainer.previousSibling;
7167 
7168 					if (endOffset > startOffset) {
7169 						endOffset = endOffset - startOffset;
7170 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
7171 						endOffset = endContainer.nodeValue.length;
7172 						startOffset = 0;
7173 					} else {
7174 						endOffset = 0;
7175 					}
7176 				}
7177 			} else {
7178 				// Split startContainer text node if needed
7179 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
7180 					startContainer = splitText(startContainer, startOffset);
7181 					startOffset = 0;
7182 				}
7183 
7184 				// Split endContainer text node if needed
7185 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
7186 					endContainer = splitText(endContainer, endOffset).previousSibling;
7187 					endOffset = endContainer.nodeValue.length;
7188 				}
7189 			}
7190 
7191 			return {
7192 				startContainer: startContainer,
7193 				startOffset: startOffset,
7194 				endContainer: endContainer,
7195 				endOffset: endOffset
7196 			};
7197 		};
7198 
7199 		/**
7200 		 * Normalizes the specified range by finding the closest best suitable caret location.
7201 		 *
7202 		 * @private
7203 		 * @param {Range} rng Range to normalize.
7204 		 * @return {Boolean} True/false if the specified range was normalized or not.
7205 		 */
7206 		this.normalize = function(rng) {
7207 			var normalized, collapsed;
7208 
7209 			function normalizeEndPoint(start) {
7210 				var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
7211 				var directionLeft, isAfterNode;
7212 
7213 				function hasBrBeforeAfter(node, left) {
7214 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
7215 
7216 					while ((node = walker[left ? 'prev' : 'next']())) {
7217 						if (node.nodeName === "BR") {
7218 							return true;
7219 						}
7220 					}
7221 				}
7222 
7223 				function isPrevNode(node, name) {
7224 					return node.previousSibling && node.previousSibling.nodeName == name;
7225 				}
7226 
7227 				// Walks the dom left/right to find a suitable text node to move the endpoint into
7228 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
7229 				function findTextNodeRelative(left, startNode) {
7230 					var walker, lastInlineElement, parentBlockContainer;
7231 
7232 					startNode = startNode || container;
7233 					parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
7234 
7235 					// Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
7236 					// This: <p><br>|</p> becomes <p>|<br></p>
7237 					if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
7238 						container = startNode.parentNode;
7239 						offset = dom.nodeIndex(startNode);
7240 						normalized = true;
7241 						return;
7242 					}
7243 
7244 					// Walk left until we hit a text node we can move to or a block/br/img
7245 					walker = new TreeWalker(startNode, parentBlockContainer);
7246 					while ((node = walker[left ? 'prev' : 'next']())) {
7247 						// Break if we hit a non content editable node
7248 						if (dom.getContentEditableParent(node) === "false") {
7249 							return;
7250 						}
7251 
7252 						// Found text node that has a length
7253 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
7254 							container = node;
7255 							offset = left ? node.nodeValue.length : 0;
7256 							normalized = true;
7257 							return;
7258 						}
7259 
7260 						// Break if we find a block or a BR/IMG/INPUT etc
7261 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
7262 							return;
7263 						}
7264 
7265 						lastInlineElement = node;
7266 					}
7267 
7268 					// Only fetch the last inline element when in caret mode for now
7269 					if (collapsed && lastInlineElement) {
7270 						container = lastInlineElement;
7271 						normalized = true;
7272 						offset = 0;
7273 					}
7274 				}
7275 
7276 				container = rng[(start ? 'start' : 'end') + 'Container'];
7277 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
7278 				isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
7279 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
7280 				directionLeft = start;
7281 
7282 				if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
7283 					directionLeft = false;
7284 				}
7285 
7286 				// If the container is a document move it to the body element
7287 				if (container.nodeType === 9) {
7288 					container = dom.getRoot();
7289 					offset = 0;
7290 				}
7291 
7292 				// If the container is body try move it into the closest text node or position
7293 				if (container === body) {
7294 					// If start is before/after a image, table etc
7295 					if (directionLeft) {
7296 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
7297 						if (node) {
7298 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
7299 								return;
7300 							}
7301 						}
7302 					}
7303 
7304 					// Resolve the index
7305 					if (container.hasChildNodes()) {
7306 						offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
7307 						container = container.childNodes[offset];
7308 						offset = 0;
7309 
7310 						// Don't walk into elements that doesn't have any child nodes like a IMG
7311 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
7312 							// Walk the DOM to find a text node to place the caret at or a BR
7313 							node = container;
7314 							walker = new TreeWalker(container, body);
7315 
7316 							do {
7317 								// Found a text node use that position
7318 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
7319 									offset = directionLeft ? 0 : node.nodeValue.length;
7320 									container = node;
7321 									normalized = true;
7322 									break;
7323 								}
7324 
7325 								// Found a BR/IMG element that we can place the caret before
7326 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
7327 									offset = dom.nodeIndex(node);
7328 									container = node.parentNode;
7329 
7330 									// Put caret after image when moving the end point
7331 									if (node.nodeName ==  "IMG" && !directionLeft) {
7332 										offset++;
7333 									}
7334 
7335 									normalized = true;
7336 									break;
7337 								}
7338 							} while ((node = (directionLeft ? walker.next() : walker.prev())));
7339 						}
7340 					}
7341 				}
7342 
7343 				// Lean the caret to the left if possible
7344 				if (collapsed) {
7345 					// So this: <b>x</b><i>|x</i>
7346 					// Becomes: <b>x|</b><i>x</i>
7347 					// Seems that only gecko has issues with this
7348 					if (container.nodeType === 3 && offset === 0) {
7349 						findTextNodeRelative(true);
7350 					}
7351 
7352 					// Lean left into empty inline elements when the caret is before a BR
7353 					// So this: <i><b></b><i>|<br></i>
7354 					// Becomes: <i><b>|</b><i><br></i>
7355 					// Seems that only gecko has issues with this.
7356 					// Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
7357 					if (container.nodeType === 1) {
7358 						node = container.childNodes[offset];
7359 
7360 						// Offset is after the containers last child
7361 						// then use the previous child for normalization
7362 						if (!node) {
7363 							node = container.childNodes[offset - 1];
7364 						}
7365 
7366 						if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
7367 							!hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
7368 							findTextNodeRelative(true, node);
7369 						}
7370 					}
7371 				}
7372 
7373 				// Lean the start of the selection right if possible
7374 				// So this: x[<b>x]</b>
7375 				// Becomes: x<b>[x]</b>
7376 				if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
7377 					findTextNodeRelative(false);
7378 				}
7379 
7380 				// Set endpoint if it was normalized
7381 				if (normalized) {
7382 					rng['set' + (start ? 'Start' : 'End')](container, offset);
7383 				}
7384 			}
7385 
7386 			collapsed = rng.collapsed;
7387 
7388 			normalizeEndPoint(true);
7389 
7390 			if (!collapsed) {
7391 				normalizeEndPoint();
7392 			}
7393 
7394 			// If it was collapsed then make sure it still is
7395 			if (normalized && collapsed) {
7396 				rng.collapse(true);
7397 			}
7398 
7399 			return normalized;
7400 		};
7401 	}
7402 
7403 	/**
7404 	 * Compares two ranges and checks if they are equal.
7405 	 *
7406 	 * @static
7407 	 * @method compareRanges
7408 	 * @param {DOMRange} rng1 First range to compare.
7409 	 * @param {DOMRange} rng2 First range to compare.
7410 	 * @return {Boolean} true/false if the ranges are equal.
7411 	 */
7412 	RangeUtils.compareRanges = function(rng1, rng2) {
7413 		if (rng1 && rng2) {
7414 			// Compare native IE ranges
7415 			if (rng1.item || rng1.duplicate) {
7416 				// Both are control ranges and the selected element matches
7417 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
7418 					return true;
7419 				}
7420 
7421 				// Both are text ranges and the range matches
7422 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
7423 					return true;
7424 				}
7425 			} else {
7426 				// Compare w3c ranges
7427 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7428 			}
7429 		}
7430 
7431 		return false;
7432 	};
7433 
7434 	return RangeUtils;
7435 });
7436 
7437 // Included from: js/tinymce/classes/NodeChange.js
7438 
7439 /**
7440  * NodeChange.js
7441  *
7442  * Copyright, Moxiecode Systems AB
7443  * Released under LGPL License.
7444  *
7445  * License: http://www.tinymce.com/license
7446  * Contributing: http://www.tinymce.com/contributing
7447  */
7448 
7449 /**
7450  * This class handles the nodechange event dispatching both manual and though selection change events.
7451  *
7452  * @class tinymce.NodeChange
7453  * @private
7454  */
7455 define("tinymce/NodeChange", [
7456 	"tinymce/dom/RangeUtils"
7457 ], function(RangeUtils) {
7458 	return function(editor) {
7459 		var lastRng, lastPath = [];
7460 
7461 		/**
7462 		 * Returns true/false if the current element path has been changed or not.
7463 		 *
7464 		 * @private
7465 		 * @return {Boolean} True if the element path is the same false if it's not.
7466 		 */
7467 		function isSameElementPath(startElm) {
7468 			var i, currentPath;
7469 
7470 			currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
7471 			if (currentPath.length === lastPath.length) {
7472 				for (i = currentPath.length; i >= 0; i--) {
7473 					if (currentPath[i] !== lastPath[i]) {
7474 						break;
7475 					}
7476 				}
7477 
7478 				if (i === -1) {
7479 					lastPath = currentPath;
7480 					return true;
7481 				}
7482 			}
7483 
7484 			lastPath = currentPath;
7485 
7486 			return false;
7487 		}
7488 
7489 		// Gecko doesn't support the "selectionchange" event
7490 		if (!('onselectionchange' in editor.getDoc())) {
7491 			editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
7492 				var nativeRng, fakeRng;
7493 
7494 				// Since DOM Ranges mutate on modification
7495 				// of the DOM we need to clone it's contents
7496 				nativeRng = editor.selection.getRng();
7497 				fakeRng = {
7498 					startContainer: nativeRng.startContainer,
7499 					startOffset: nativeRng.startOffset,
7500 					endContainer: nativeRng.endContainer,
7501 					endOffset: nativeRng.endOffset
7502 				};
7503 
7504 				// Always treat nodechange as a selectionchange since applying
7505 				// formatting to the current range wouldn't update the range but it's parent
7506 				if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
7507 					editor.fire('SelectionChange');
7508 				}
7509 
7510 				lastRng = fakeRng;
7511 			});
7512 		}
7513 
7514 		// IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
7515 		// When the contextmenu event fires the selection is located at the right location
7516 		editor.on('contextmenu', function() {
7517 			editor.fire('SelectionChange');
7518 		});
7519 
7520 		editor.on('SelectionChange', function() {
7521 			var startElm = editor.selection.getStart(true);
7522 
7523 			// Fire a nodechange only when the selection isn't collapsed since focusout will collapse and remove the selection
7524 			if (!editor.selection.isCollapsed() && !isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
7525 				editor.nodeChanged({selectionChange: true});
7526 			}
7527 		});
7528 
7529 		// Fire an extra nodeChange on mouseup for compatibility reasons
7530 		editor.on('MouseUp', function(e) {
7531 			if (!e.isDefaultPrevented()) {
7532 				// Delay nodeChanged call for WebKit edge case issue where the range
7533 				// isn't updated until after you click outside a selected image
7534 				setTimeout(function() {
7535 					editor.nodeChanged();
7536 				}, 0);
7537 			}
7538 		});
7539 
7540 		/**
7541 		 * Distpaches out a onNodeChange event to all observers. This method should be called when you
7542 		 * need to update the UI states or element path etc.
7543 		 *
7544 		 * @method nodeChanged
7545 		 * @param {Object} args Optional args to pass to NodeChange event handlers.
7546 		 */
7547 		this.nodeChanged = function(args) {
7548 			var selection = editor.selection, node, parents, root;
7549 
7550 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
7551 			if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.settings.readonly) {
7552 				// Get start node
7553 				root = editor.getBody();
7554 				node = selection.getStart() || root;
7555 				node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node;
7556 
7557 				// Edge case for <p>|<img></p>
7558 				if (node.nodeName == 'IMG' && selection.isCollapsed()) {
7559 					node = node.parentNode;
7560 				}
7561 
7562 				// Get parents and add them to object
7563 				parents = [];
7564 				editor.dom.getParent(node, function(node) {
7565 					if (node === root) {
7566 						return true;
7567 					}
7568 
7569 					parents.push(node);
7570 				});
7571 
7572 				args = args || {};
7573 				args.element = node;
7574 				args.parents = parents;
7575 
7576 				editor.fire('NodeChange', args);
7577 			}
7578 		};
7579 	};
7580 });
7581 
7582 // Included from: js/tinymce/classes/html/Node.js
7583 
7584 /**
7585  * Node.js
7586  *
7587  * Copyright, Moxiecode Systems AB
7588  * Released under LGPL License.
7589  *
7590  * License: http://www.tinymce.com/license
7591  * Contributing: http://www.tinymce.com/contributing
7592  */
7593 
7594 /**
7595  * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
7596  *
7597  * @example
7598  * var node = new tinymce.html.Node('strong', 1);
7599  * someRoot.append(node);
7600  *
7601  * @class tinymce.html.Node
7602  * @version 3.4
7603  */
7604 define("tinymce/html/Node", [], function() {
7605 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
7606 		'#text': 3,
7607 		'#comment': 8,
7608 		'#cdata': 4,
7609 		'#pi': 7,
7610 		'#doctype': 10,
7611 		'#document-fragment': 11
7612 	};
7613 
7614 	// Walks the tree left/right
7615 	function walk(node, root_node, prev) {
7616 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
7617 
7618 		// Walk into nodes if it has a start
7619 		if (node[startName]) {
7620 			return node[startName];
7621 		}
7622 
7623 		// Return the sibling if it has one
7624 		if (node !== root_node) {
7625 			sibling = node[siblingName];
7626 
7627 			if (sibling) {
7628 				return sibling;
7629 			}
7630 
7631 			// Walk up the parents to look for siblings
7632 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
7633 				sibling = parent[siblingName];
7634 
7635 				if (sibling) {
7636 					return sibling;
7637 				}
7638 			}
7639 		}
7640 	}
7641 
7642 	/**
7643 	 * Constructs a new Node instance.
7644 	 *
7645 	 * @constructor
7646 	 * @method Node
7647 	 * @param {String} name Name of the node type.
7648 	 * @param {Number} type Numeric type representing the node.
7649 	 */
7650 	function Node(name, type) {
7651 		this.name = name;
7652 		this.type = type;
7653 
7654 		if (type === 1) {
7655 			this.attributes = [];
7656 			this.attributes.map = {};
7657 		}
7658 	}
7659 
7660 	Node.prototype = {
7661 		/**
7662 		 * Replaces the current node with the specified one.
7663 		 *
7664 		 * @example
7665 		 * someNode.replace(someNewNode);
7666 		 *
7667 		 * @method replace
7668 		 * @param {tinymce.html.Node} node Node to replace the current node with.
7669 		 * @return {tinymce.html.Node} The old node that got replaced.
7670 		 */
7671 		replace: function(node) {
7672 			var self = this;
7673 
7674 			if (node.parent) {
7675 				node.remove();
7676 			}
7677 
7678 			self.insert(node, self);
7679 			self.remove();
7680 
7681 			return self;
7682 		},
7683 
7684 		/**
7685 		 * Gets/sets or removes an attribute by name.
7686 		 *
7687 		 * @example
7688 		 * someNode.attr("name", "value"); // Sets an attribute
7689 		 * console.log(someNode.attr("name")); // Gets an attribute
7690 		 * someNode.attr("name", null); // Removes an attribute
7691 		 *
7692 		 * @method attr
7693 		 * @param {String} name Attribute name to set or get.
7694 		 * @param {String} value Optional value to set.
7695 		 * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
7696 		 */
7697 		attr: function(name, value) {
7698 			var self = this, attrs, i, undef;
7699 
7700 			if (typeof name !== "string") {
7701 				for (i in name) {
7702 					self.attr(i, name[i]);
7703 				}
7704 
7705 				return self;
7706 			}
7707 
7708 			if ((attrs = self.attributes)) {
7709 				if (value !== undef) {
7710 					// Remove attribute
7711 					if (value === null) {
7712 						if (name in attrs.map) {
7713 							delete attrs.map[name];
7714 
7715 							i = attrs.length;
7716 							while (i--) {
7717 								if (attrs[i].name === name) {
7718 									attrs = attrs.splice(i, 1);
7719 									return self;
7720 								}
7721 							}
7722 						}
7723 
7724 						return self;
7725 					}
7726 
7727 					// Set attribute
7728 					if (name in attrs.map) {
7729 						// Set attribute
7730 						i = attrs.length;
7731 						while (i--) {
7732 							if (attrs[i].name === name) {
7733 								attrs[i].value = value;
7734 								break;
7735 							}
7736 						}
7737 					} else {
7738 						attrs.push({name: name, value: value});
7739 					}
7740 
7741 					attrs.map[name] = value;
7742 
7743 					return self;
7744 				} else {
7745 					return attrs.map[name];
7746 				}
7747 			}
7748 		},
7749 
7750 		/**
7751 		 * Does a shallow clones the node into a new node. It will also exclude id attributes since
7752 		 * there should only be one id per document.
7753 		 *
7754 		 * @example
7755 		 * var clonedNode = node.clone();
7756 		 *
7757 		 * @method clone
7758 		 * @return {tinymce.html.Node} New copy of the original node.
7759 		 */
7760 		clone: function() {
7761 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
7762 
7763 			// Clone element attributes
7764 			if ((selfAttrs = self.attributes)) {
7765 				cloneAttrs = [];
7766 				cloneAttrs.map = {};
7767 
7768 				for (i = 0, l = selfAttrs.length; i < l; i++) {
7769 					selfAttr = selfAttrs[i];
7770 
7771 					// Clone everything except id
7772 					if (selfAttr.name !== 'id') {
7773 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
7774 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
7775 					}
7776 				}
7777 
7778 				clone.attributes = cloneAttrs;
7779 			}
7780 
7781 			clone.value = self.value;
7782 			clone.shortEnded = self.shortEnded;
7783 
7784 			return clone;
7785 		},
7786 
7787 		/**
7788 		 * Wraps the node in in another node.
7789 		 *
7790 		 * @example
7791 		 * node.wrap(wrapperNode);
7792 		 *
7793 		 * @method wrap
7794 		 */
7795 		wrap: function(wrapper) {
7796 			var self = this;
7797 
7798 			self.parent.insert(wrapper, self);
7799 			wrapper.append(self);
7800 
7801 			return self;
7802 		},
7803 
7804 		/**
7805 		 * Unwraps the node in other words it removes the node but keeps the children.
7806 		 *
7807 		 * @example
7808 		 * node.unwrap();
7809 		 *
7810 		 * @method unwrap
7811 		 */
7812 		unwrap: function() {
7813 			var self = this, node, next;
7814 
7815 			for (node = self.firstChild; node;) {
7816 				next = node.next;
7817 				self.insert(node, self, true);
7818 				node = next;
7819 			}
7820 
7821 			self.remove();
7822 		},
7823 
7824 		/**
7825 		 * Removes the node from it's parent.
7826 		 *
7827 		 * @example
7828 		 * node.remove();
7829 		 *
7830 		 * @method remove
7831 		 * @return {tinymce.html.Node} Current node that got removed.
7832 		 */
7833 		remove: function() {
7834 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
7835 
7836 			if (parent) {
7837 				if (parent.firstChild === self) {
7838 					parent.firstChild = next;
7839 
7840 					if (next) {
7841 						next.prev = null;
7842 					}
7843 				} else {
7844 					prev.next = next;
7845 				}
7846 
7847 				if (parent.lastChild === self) {
7848 					parent.lastChild = prev;
7849 
7850 					if (prev) {
7851 						prev.next = null;
7852 					}
7853 				} else {
7854 					next.prev = prev;
7855 				}
7856 
7857 				self.parent = self.next = self.prev = null;
7858 			}
7859 
7860 			return self;
7861 		},
7862 
7863 		/**
7864 		 * Appends a new node as a child of the current node.
7865 		 *
7866 		 * @example
7867 		 * node.append(someNode);
7868 		 *
7869 		 * @method append
7870 		 * @param {tinymce.html.Node} node Node to append as a child of the current one.
7871 		 * @return {tinymce.html.Node} The node that got appended.
7872 		 */
7873 		append: function(node) {
7874 			var self = this, last;
7875 
7876 			if (node.parent) {
7877 				node.remove();
7878 			}
7879 
7880 			last = self.lastChild;
7881 			if (last) {
7882 				last.next = node;
7883 				node.prev = last;
7884 				self.lastChild = node;
7885 			} else {
7886 				self.lastChild = self.firstChild = node;
7887 			}
7888 
7889 			node.parent = self;
7890 
7891 			return node;
7892 		},
7893 
7894 		/**
7895 		 * Inserts a node at a specific position as a child of the current node.
7896 		 *
7897 		 * @example
7898 		 * parentNode.insert(newChildNode, oldChildNode);
7899 		 *
7900 		 * @method insert
7901 		 * @param {tinymce.html.Node} node Node to insert as a child of the current node.
7902 		 * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
7903 		 * @param {Boolean} before Optional state to insert the node before the reference node.
7904 		 * @return {tinymce.html.Node} The node that got inserted.
7905 		 */
7906 		insert: function(node, ref_node, before) {
7907 			var parent;
7908 
7909 			if (node.parent) {
7910 				node.remove();
7911 			}
7912 
7913 			parent = ref_node.parent || this;
7914 
7915 			if (before) {
7916 				if (ref_node === parent.firstChild) {
7917 					parent.firstChild = node;
7918 				} else {
7919 					ref_node.prev.next = node;
7920 				}
7921 
7922 				node.prev = ref_node.prev;
7923 				node.next = ref_node;
7924 				ref_node.prev = node;
7925 			} else {
7926 				if (ref_node === parent.lastChild) {
7927 					parent.lastChild = node;
7928 				} else {
7929 					ref_node.next.prev = node;
7930 				}
7931 
7932 				node.next = ref_node.next;
7933 				node.prev = ref_node;
7934 				ref_node.next = node;
7935 			}
7936 
7937 			node.parent = parent;
7938 
7939 			return node;
7940 		},
7941 
7942 		/**
7943 		 * Get all children by name.
7944 		 *
7945 		 * @method getAll
7946 		 * @param {String} name Name of the child nodes to collect.
7947 		 * @return {Array} Array with child nodes matchin the specified name.
7948 		 */
7949 		getAll: function(name) {
7950 			var self = this, node, collection = [];
7951 
7952 			for (node = self.firstChild; node; node = walk(node, self)) {
7953 				if (node.name === name) {
7954 					collection.push(node);
7955 				}
7956 			}
7957 
7958 			return collection;
7959 		},
7960 
7961 		/**
7962 		 * Removes all children of the current node.
7963 		 *
7964 		 * @method empty
7965 		 * @return {tinymce.html.Node} The current node that got cleared.
7966 		 */
7967 		empty: function() {
7968 			var self = this, nodes, i, node;
7969 
7970 			// Remove all children
7971 			if (self.firstChild) {
7972 				nodes = [];
7973 
7974 				// Collect the children
7975 				for (node = self.firstChild; node; node = walk(node, self)) {
7976 					nodes.push(node);
7977 				}
7978 
7979 				// Remove the children
7980 				i = nodes.length;
7981 				while (i--) {
7982 					node = nodes[i];
7983 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
7984 				}
7985 			}
7986 
7987 			self.firstChild = self.lastChild = null;
7988 
7989 			return self;
7990 		},
7991 
7992 		/**
7993 		 * Returns true/false if the node is to be considered empty or not.
7994 		 *
7995 		 * @example
7996 		 * node.isEmpty({img: true});
7997 		 * @method isEmpty
7998 		 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
7999 		 * @return {Boolean} true/false if the node is empty or not.
8000 		 */
8001 		isEmpty: function(elements) {
8002 			var self = this, node = self.firstChild, i, name;
8003 
8004 			if (node) {
8005 				do {
8006 					if (node.type === 1) {
8007 						// Ignore bogus elements
8008 						if (node.attributes.map['data-mce-bogus']) {
8009 							continue;
8010 						}
8011 
8012 						// Keep empty elements like <img />
8013 						if (elements[node.name]) {
8014 							return false;
8015 						}
8016 
8017 						// Keep bookmark nodes and name attribute like <a name="1"></a>
8018 						i = node.attributes.length;
8019 						while (i--) {
8020 							name = node.attributes[i].name;
8021 							if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
8022 								return false;
8023 							}
8024 						}
8025 					}
8026 
8027 					// Keep comments
8028 					if (node.type === 8) {
8029 						return false;
8030 					}
8031 
8032 					// Keep non whitespace text nodes
8033 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
8034 						return false;
8035 					}
8036 				} while ((node = walk(node, self)));
8037 			}
8038 
8039 			return true;
8040 		},
8041 
8042 		/**
8043 		 * Walks to the next or previous node and returns that node or null if it wasn't found.
8044 		 *
8045 		 * @method walk
8046 		 * @param {Boolean} prev Optional previous node state defaults to false.
8047 		 * @return {tinymce.html.Node} Node that is next to or previous of the current node.
8048 		 */
8049 		walk: function(prev) {
8050 			return walk(this, null, prev);
8051 		}
8052 	};
8053 
8054 	/**
8055 	 * Creates a node of a specific type.
8056 	 *
8057 	 * @static
8058 	 * @method create
8059 	 * @param {String} name Name of the node type to create for example "b" or "#text".
8060 	 * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
8061 	 */
8062 	Node.create = function(name, attrs) {
8063 		var node, attrName;
8064 
8065 		// Create node
8066 		node = new Node(name, typeLookup[name] || 1);
8067 
8068 		// Add attributes if needed
8069 		if (attrs) {
8070 			for (attrName in attrs) {
8071 				node.attr(attrName, attrs[attrName]);
8072 			}
8073 		}
8074 
8075 		return node;
8076 	};
8077 
8078 	return Node;
8079 });
8080 
8081 // Included from: js/tinymce/classes/html/Schema.js
8082 
8083 /**
8084  * Schema.js
8085  *
8086  * Copyright, Moxiecode Systems AB
8087  * Released under LGPL License.
8088  *
8089  * License: http://www.tinymce.com/license
8090  * Contributing: http://www.tinymce.com/contributing
8091  */
8092 
8093 /**
8094  * Schema validator class.
8095  *
8096  * @class tinymce.html.Schema
8097  * @example
8098  *  if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
8099  *    alert('span is valid child of p.');
8100  *
8101  *  if (tinymce.activeEditor.schema.getElementRule('p'))
8102  *    alert('P is a valid element.');
8103  *
8104  * @class tinymce.html.Schema
8105  * @version 3.4
8106  */
8107 define("tinymce/html/Schema", [
8108 	"tinymce/util/Tools"
8109 ], function(Tools) {
8110 	var mapCache = {}, dummyObj = {};
8111 	var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
8112 
8113 	function split(items, delim) {
8114 		return items ? items.split(delim || ' ') : [];
8115 	}
8116 
8117 	/**
8118 	 * Builds a schema lookup table
8119 	 *
8120 	 * @private
8121 	 * @param {String} type html4, html5 or html5-strict schema type.
8122 	 * @return {Object} Schema lookup table.
8123 	 */
8124 	function compileSchema(type) {
8125 		var schema = {}, globalAttributes, blockContent;
8126 		var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
8127 
8128 		function add(name, attributes, children) {
8129 			var ni, i, attributesOrder, args = arguments;
8130 
8131 			function arrayToMap(array, obj) {
8132 				var map = {}, i, l;
8133 
8134 				for (i = 0, l = array.length; i < l; i++) {
8135 					map[array[i]] = obj || {};
8136 				}
8137 
8138 				return map;
8139 			}
8140 
8141 			children = children || [];
8142 			attributes = attributes || "";
8143 
8144 			if (typeof(children) === "string") {
8145 				children = split(children);
8146 			}
8147 
8148 			// Split string children
8149 			for (i = 3; i < args.length; i++) {
8150 				if (typeof(args[i]) === "string") {
8151 					args[i] = split(args[i]);
8152 				}
8153 
8154 				children.push.apply(children, args[i]);
8155 			}
8156 
8157 			name = split(name);
8158 			ni = name.length;
8159 			while (ni--) {
8160 				attributesOrder = [].concat(globalAttributes, split(attributes));
8161 				schema[name[ni]] = {
8162 					attributes: arrayToMap(attributesOrder),
8163 					attributesOrder: attributesOrder,
8164 					children: arrayToMap(children, dummyObj)
8165 				};
8166 			}
8167 		}
8168 
8169 		function addAttrs(name, attributes) {
8170 			var ni, schemaItem, i, l;
8171 
8172 			name = split(name);
8173 			ni = name.length;
8174 			attributes = split(attributes);
8175 			while (ni--) {
8176 				schemaItem = schema[name[ni]];
8177 				for (i = 0, l = attributes.length; i < l; i++) {
8178 					schemaItem.attributes[attributes[i]] = {};
8179 					schemaItem.attributesOrder.push(attributes[i]);
8180 				}
8181 			}
8182 		}
8183 
8184 		// Use cached schema
8185 		if (mapCache[type]) {
8186 			return mapCache[type];
8187 		}
8188 
8189 		// Attributes present on all elements
8190 		globalAttributes = split("id accesskey class dir lang style tabindex title");
8191 
8192 		// Event attributes can be opt-in/opt-out
8193 		/*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
8194 				"ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
8195 				"onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
8196 				"onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
8197 				"onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
8198 				"onwaiting"
8199 		);*/
8200 
8201 		// Block content elements
8202 		blockContent = split(
8203 			"address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
8204 		);
8205 
8206 		// Phrasing content elements from the HTML5 spec (inline)
8207 		phrasingContent = split(
8208 			"a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
8209 			"label map noscript object q s samp script select small span strong sub sup " +
8210 			"textarea u var #text #comment"
8211 		);
8212 
8213 		// Add HTML5 items to globalAttributes, blockContent, phrasingContent
8214 		if (type != "html4") {
8215 			globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
8216 				"hidden spellcheck translate"));
8217 			blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
8218 			phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
8219 				"video ruby bdi keygen"));
8220 		}
8221 
8222 		// Add HTML4 elements unless it's html5-strict
8223 		if (type != "html5-strict") {
8224 			globalAttributes.push("xml:lang");
8225 
8226 			html4PhrasingContent = split("acronym applet basefont big font strike tt");
8227 			phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
8228 
8229 			each(html4PhrasingContent, function(name) {
8230 				add(name, "", phrasingContent);
8231 			});
8232 
8233 			html4BlockContent = split("center dir isindex noframes");
8234 			blockContent.push.apply(blockContent, html4BlockContent);
8235 
8236 			// Flow content elements from the HTML5 spec (block+inline)
8237 			flowContent = [].concat(blockContent, phrasingContent);
8238 
8239 			each(html4BlockContent, function(name) {
8240 				add(name, "", flowContent);
8241 			});
8242 		}
8243 
8244 		// Flow content elements from the HTML5 spec (block+inline)
8245 		flowContent = flowContent || [].concat(blockContent, phrasingContent);
8246 
8247 		// HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
8248 		// Schema items <element name>, <specific attributes>, <children ..>
8249 		add("html", "manifest", "head body");
8250 		add("head", "", "base command link meta noscript script style title");
8251 		add("title hr noscript br");
8252 		add("base", "href target");
8253 		add("link", "href rel media hreflang type sizes hreflang");
8254 		add("meta", "name http-equiv content charset");
8255 		add("style", "media type scoped");
8256 		add("script", "src async defer type charset");
8257 		add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
8258 				"onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
8259 				"onpopstate onresize onscroll onstorage onunload", flowContent);
8260 		add("address dt dd div caption", "", flowContent);
8261 		add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent);
8262 		add("blockquote", "cite", flowContent);
8263 		add("ol", "reversed start type", "li");
8264 		add("ul", "", "li");
8265 		add("li", "value", flowContent);
8266 		add("dl", "", "dt dd");
8267 		add("a", "href target rel media hreflang type", phrasingContent);
8268 		add("q", "cite", phrasingContent);
8269 		add("ins del", "cite datetime", flowContent);
8270 		add("img", "src alt usemap ismap width height");
8271 		add("iframe", "src name width height", flowContent);
8272 		add("embed", "src type width height");
8273 		add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
8274 		add("param", "name value");
8275 		add("map", "name", flowContent, "area");
8276 		add("area", "alt coords shape href target rel media hreflang type");
8277 		add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
8278 		add("colgroup", "span", "col");
8279 		add("col", "span");
8280 		add("tbody thead tfoot", "", "tr");
8281 		add("tr", "", "td th");
8282 		add("td", "colspan rowspan headers", flowContent);
8283 		add("th", "colspan rowspan headers scope abbr", flowContent);
8284 		add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
8285 		add("fieldset", "disabled form name", flowContent, "legend");
8286 		add("label", "form for", phrasingContent);
8287 		add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
8288 				"formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
8289 		);
8290 		add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
8291 			type == "html4" ? flowContent : phrasingContent);
8292 		add("select", "disabled form multiple name required size", "option optgroup");
8293 		add("optgroup", "disabled label", "option");
8294 		add("option", "disabled label selected value");
8295 		add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
8296 		add("menu", "type label", flowContent, "li");
8297 		add("noscript", "", flowContent);
8298 
8299 		// Extend with HTML5 elements
8300 		if (type != "html4") {
8301 			add("wbr");
8302 			add("ruby", "", phrasingContent, "rt rp");
8303 			add("figcaption", "", flowContent);
8304 			add("mark rt rp summary bdi", "", phrasingContent);
8305 			add("canvas", "width height", flowContent);
8306 			add("video", "src crossorigin poster preload autoplay mediagroup loop " +
8307 				"muted controls width height buffered", flowContent, "track source");
8308 			add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
8309 			add("source", "src type media");
8310 			add("track", "kind src srclang label default");
8311 			add("datalist", "", phrasingContent, "option");
8312 			add("article section nav aside header footer", "", flowContent);
8313 			add("hgroup", "", "h1 h2 h3 h4 h5 h6");
8314 			add("figure", "", flowContent, "figcaption");
8315 			add("time", "datetime", phrasingContent);
8316 			add("dialog", "open", flowContent);
8317 			add("command", "type label icon disabled checked radiogroup command");
8318 			add("output", "for form name", phrasingContent);
8319 			add("progress", "value max", phrasingContent);
8320 			add("meter", "value min max low high optimum", phrasingContent);
8321 			add("details", "open", flowContent, "summary");
8322 			add("keygen", "autofocus challenge disabled form keytype name");
8323 		}
8324 
8325 		// Extend with HTML4 attributes unless it's html5-strict
8326 		if (type != "html5-strict") {
8327 			addAttrs("script", "language xml:space");
8328 			addAttrs("style", "xml:space");
8329 			addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
8330 			addAttrs("embed", "align name hspace vspace");
8331 			addAttrs("param", "valuetype type");
8332 			addAttrs("a", "charset name rev shape coords");
8333 			addAttrs("br", "clear");
8334 			addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
8335 			addAttrs("img", "name longdesc align border hspace vspace");
8336 			addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
8337 			addAttrs("font basefont", "size color face");
8338 			addAttrs("input", "usemap align");
8339 			addAttrs("select", "onchange");
8340 			addAttrs("textarea");
8341 			addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
8342 			addAttrs("ul", "type compact");
8343 			addAttrs("li", "type");
8344 			addAttrs("ol dl menu dir", "compact");
8345 			addAttrs("pre", "width xml:space");
8346 			addAttrs("hr", "align noshade size width");
8347 			addAttrs("isindex", "prompt");
8348 			addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
8349 			addAttrs("col", "width align char charoff valign");
8350 			addAttrs("colgroup", "width align char charoff valign");
8351 			addAttrs("thead", "align char charoff valign");
8352 			addAttrs("tr", "align char charoff valign bgcolor");
8353 			addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
8354 			addAttrs("form", "accept");
8355 			addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
8356 			addAttrs("tfoot", "align char charoff valign");
8357 			addAttrs("tbody", "align char charoff valign");
8358 			addAttrs("area", "nohref");
8359 			addAttrs("body", "background bgcolor text link vlink alink");
8360 		}
8361 
8362 		// Extend with HTML5 attributes unless it's html4
8363 		if (type != "html4") {
8364 			addAttrs("input button select textarea", "autofocus");
8365 			addAttrs("input textarea", "placeholder");
8366 			addAttrs("a", "download");
8367 			addAttrs("link script img", "crossorigin");
8368 			addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
8369 		}
8370 
8371 		// Special: iframe, ruby, video, audio, label
8372 
8373 		// Delete children of the same name from it's parent
8374 		// For example: form can't have a child of the name form
8375 		each(split('a form meter progress dfn'), function(name) {
8376 			if (schema[name]) {
8377 				delete schema[name].children[name];
8378 			}
8379 		});
8380 
8381 		// Delete header, footer, sectioning and heading content descendants
8382 		/*each('dt th address', function(name) {
8383 			delete schema[name].children[name];
8384 		});*/
8385 
8386 		// Caption can't have tables
8387 		delete schema.caption.children.table;
8388 
8389 		// TODO: LI:s can only have value if parent is OL
8390 
8391 		// TODO: Handle transparent elements
8392 		// a ins del canvas map
8393 
8394 		mapCache[type] = schema;
8395 
8396 		return schema;
8397 	}
8398 
8399 	function compileElementMap(value, mode) {
8400 		var styles;
8401 
8402 		if (value) {
8403 			styles = {};
8404 
8405 			if (typeof value == 'string') {
8406 				value = {
8407 					'*': value
8408 				};
8409 			}
8410 
8411 			// Convert styles into a rule list
8412 			each(value, function(value, key) {
8413 				styles[key] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
8414 			});
8415 		}
8416 
8417 		return styles;
8418 	}
8419 
8420 	/**
8421 	 * Constructs a new Schema instance.
8422 	 *
8423 	 * @constructor
8424 	 * @method Schema
8425 	 * @param {Object} settings Name/value settings object.
8426 	 */
8427 	return function(settings) {
8428 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
8429 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
8430 		var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, textInlineElementsMap;
8431 		var customElementsMap = {}, specialElements = {};
8432 
8433 		// Creates an lookup table map object for the specified option or the default value
8434 		function createLookupTable(option, default_value, extendWith) {
8435 			var value = settings[option];
8436 
8437 			if (!value) {
8438 				// Get cached default map or make it if needed
8439 				value = mapCache[option];
8440 
8441 				if (!value) {
8442 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
8443 					value = extend(value, extendWith);
8444 
8445 					mapCache[option] = value;
8446 				}
8447 			} else {
8448 				// Create custom map
8449 				value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
8450 			}
8451 
8452 			return value;
8453 		}
8454 
8455 		settings = settings || {};
8456 		schemaItems = compileSchema(settings.schema);
8457 
8458 		// Allow all elements and attributes if verify_html is set to false
8459 		if (settings.verify_html === false) {
8460 			settings.valid_elements = '*[*]';
8461 		}
8462 
8463 		validStyles = compileElementMap(settings.valid_styles);
8464 		invalidStyles = compileElementMap(settings.invalid_styles, 'map');
8465 		validClasses = compileElementMap(settings.valid_classes, 'map');
8466 
8467 		// Setup map objects
8468 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
8469 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
8470 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
8471 			'meta param embed source wbr track');
8472 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
8473 			'noshade nowrap readonly selected autoplay loop controls');
8474 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
8475 		textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
8476 						'blockquote center dir fieldset header footer article section hgroup aside nav figure');
8477 		blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
8478 						'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
8479 						'datalist select optgroup', textBlockElementsMap);
8480 		textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
8481 										'dfn code mark q sup sub samp');
8482 
8483 		each((settings.special || 'script noscript style textarea').split(' '), function(name) {
8484 			specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
8485 		});
8486 
8487 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
8488 		function patternToRegExp(str) {
8489 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
8490 		}
8491 
8492 		// Parses the specified valid_elements string and adds to the current rules
8493 		// This function is a bit hard to read since it's heavily optimized for speed
8494 		function addValidElements(validElements) {
8495 			var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
8496 				prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
8497 				elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
8498 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
8499 				hasPatternsRegExp = /[*?+]/;
8500 
8501 			if (validElements) {
8502 				// Split valid elements into an array with rules
8503 				validElements = split(validElements, ',');
8504 
8505 				if (elements['@']) {
8506 					globalAttributes = elements['@'].attributes;
8507 					globalAttributesOrder = elements['@'].attributesOrder;
8508 				}
8509 
8510 				// Loop all rules
8511 				for (ei = 0, el = validElements.length; ei < el; ei++) {
8512 					// Parse element rule
8513 					matches = elementRuleRegExp.exec(validElements[ei]);
8514 					if (matches) {
8515 						// Setup local names for matches
8516 						prefix = matches[1];
8517 						elementName = matches[2];
8518 						outputName = matches[3];
8519 						attrData = matches[5];
8520 
8521 						// Create new attributes and attributesOrder
8522 						attributes = {};
8523 						attributesOrder = [];
8524 
8525 						// Create the new element
8526 						element = {
8527 							attributes: attributes,
8528 							attributesOrder: attributesOrder
8529 						};
8530 
8531 						// Padd empty elements prefix
8532 						if (prefix === '#') {
8533 							element.paddEmpty = true;
8534 						}
8535 
8536 						// Remove empty elements prefix
8537 						if (prefix === '-') {
8538 							element.removeEmpty = true;
8539 						}
8540 
8541 						if (matches[4] === '!') {
8542 							element.removeEmptyAttrs = true;
8543 						}
8544 
8545 						// Copy attributes from global rule into current rule
8546 						if (globalAttributes) {
8547 							for (key in globalAttributes) {
8548 								attributes[key] = globalAttributes[key];
8549 							}
8550 
8551 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
8552 						}
8553 
8554 						// Attributes defined
8555 						if (attrData) {
8556 							attrData = split(attrData, '|');
8557 							for (ai = 0, al = attrData.length; ai < al; ai++) {
8558 								matches = attrRuleRegExp.exec(attrData[ai]);
8559 								if (matches) {
8560 									attr = {};
8561 									attrType = matches[1];
8562 									attrName = matches[2].replace(/::/g, ':');
8563 									prefix = matches[3];
8564 									value = matches[4];
8565 
8566 									// Required
8567 									if (attrType === '!') {
8568 										element.attributesRequired = element.attributesRequired || [];
8569 										element.attributesRequired.push(attrName);
8570 										attr.required = true;
8571 									}
8572 
8573 									// Denied from global
8574 									if (attrType === '-') {
8575 										delete attributes[attrName];
8576 										attributesOrder.splice(inArray(attributesOrder, attrName), 1);
8577 										continue;
8578 									}
8579 
8580 									// Default value
8581 									if (prefix) {
8582 										// Default value
8583 										if (prefix === '=') {
8584 											element.attributesDefault = element.attributesDefault || [];
8585 											element.attributesDefault.push({name: attrName, value: value});
8586 											attr.defaultValue = value;
8587 										}
8588 
8589 										// Forced value
8590 										if (prefix === ':') {
8591 											element.attributesForced = element.attributesForced || [];
8592 											element.attributesForced.push({name: attrName, value: value});
8593 											attr.forcedValue = value;
8594 										}
8595 
8596 										// Required values
8597 										if (prefix === '<') {
8598 											attr.validValues = makeMap(value, '?');
8599 										}
8600 									}
8601 
8602 									// Check for attribute patterns
8603 									if (hasPatternsRegExp.test(attrName)) {
8604 										element.attributePatterns = element.attributePatterns || [];
8605 										attr.pattern = patternToRegExp(attrName);
8606 										element.attributePatterns.push(attr);
8607 									} else {
8608 										// Add attribute to order list if it doesn't already exist
8609 										if (!attributes[attrName]) {
8610 											attributesOrder.push(attrName);
8611 										}
8612 
8613 										attributes[attrName] = attr;
8614 									}
8615 								}
8616 							}
8617 						}
8618 
8619 						// Global rule, store away these for later usage
8620 						if (!globalAttributes && elementName == '@') {
8621 							globalAttributes = attributes;
8622 							globalAttributesOrder = attributesOrder;
8623 						}
8624 
8625 						// Handle substitute elements such as b/strong
8626 						if (outputName) {
8627 							element.outputName = elementName;
8628 							elements[outputName] = element;
8629 						}
8630 
8631 						// Add pattern or exact element
8632 						if (hasPatternsRegExp.test(elementName)) {
8633 							element.pattern = patternToRegExp(elementName);
8634 							patternElements.push(element);
8635 						} else {
8636 							elements[elementName] = element;
8637 						}
8638 					}
8639 				}
8640 			}
8641 		}
8642 
8643 		function setValidElements(validElements) {
8644 			elements = {};
8645 			patternElements = [];
8646 
8647 			addValidElements(validElements);
8648 
8649 			each(schemaItems, function(element, name) {
8650 				children[name] = element.children;
8651 			});
8652 		}
8653 
8654 		// Adds custom non HTML elements to the schema
8655 		function addCustomElements(customElements) {
8656 			var customElementRegExp = /^(~)?(.+)$/;
8657 
8658 			if (customElements) {
8659 				// Flush cached items since we are altering the default maps
8660 				mapCache.text_block_elements = mapCache.block_elements = null;
8661 
8662 				each(split(customElements, ','), function(rule) {
8663 					var matches = customElementRegExp.exec(rule),
8664 						inline = matches[1] === '~',
8665 						cloneName = inline ? 'span' : 'div',
8666 						name = matches[2];
8667 
8668 					children[name] = children[cloneName];
8669 					customElementsMap[name] = cloneName;
8670 
8671 					// If it's not marked as inline then add it to valid block elements
8672 					if (!inline) {
8673 						blockElementsMap[name.toUpperCase()] = {};
8674 						blockElementsMap[name] = {};
8675 					}
8676 
8677 					// Add elements clone if needed
8678 					if (!elements[name]) {
8679 						var customRule = elements[cloneName];
8680 
8681 						customRule = extend({}, customRule);
8682 						delete customRule.removeEmptyAttrs;
8683 						delete customRule.removeEmpty;
8684 
8685 						elements[name] = customRule;
8686 					}
8687 
8688 					// Add custom elements at span/div positions
8689 					each(children, function(element, elmName) {
8690 						if (element[cloneName]) {
8691 							children[elmName] = element = extend({}, children[elmName]);
8692 							element[name] = element[cloneName];
8693 						}
8694 					});
8695 				});
8696 			}
8697 		}
8698 
8699 		// Adds valid children to the schema object
8700 		function addValidChildren(validChildren) {
8701 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
8702 
8703 			if (validChildren) {
8704 				each(split(validChildren, ','), function(rule) {
8705 					var matches = childRuleRegExp.exec(rule), parent, prefix;
8706 
8707 					if (matches) {
8708 						prefix = matches[1];
8709 
8710 						// Add/remove items from default
8711 						if (prefix) {
8712 							parent = children[matches[2]];
8713 						} else {
8714 							parent = children[matches[2]] = {'#comment': {}};
8715 						}
8716 
8717 						parent = children[matches[2]];
8718 
8719 						each(split(matches[3], '|'), function(child) {
8720 							if (prefix === '-') {
8721 								// Clone the element before we delete
8722 								// things in it to not mess up default schemas
8723 								children[matches[2]] = parent = extend({}, children[matches[2]]);
8724 
8725 								delete parent[child];
8726 							} else {
8727 								parent[child] = {};
8728 							}
8729 						});
8730 					}
8731 				});
8732 			}
8733 		}
8734 
8735 		function getElementRule(name) {
8736 			var element = elements[name], i;
8737 
8738 			// Exact match found
8739 			if (element) {
8740 				return element;
8741 			}
8742 
8743 			// No exact match then try the patterns
8744 			i = patternElements.length;
8745 			while (i--) {
8746 				element = patternElements[i];
8747 
8748 				if (element.pattern.test(name)) {
8749 					return element;
8750 				}
8751 			}
8752 		}
8753 
8754 		if (!settings.valid_elements) {
8755 			// No valid elements defined then clone the elements from the schema spec
8756 			each(schemaItems, function(element, name) {
8757 				elements[name] = {
8758 					attributes: element.attributes,
8759 					attributesOrder: element.attributesOrder
8760 				};
8761 
8762 				children[name] = element.children;
8763 			});
8764 
8765 			// Switch these on HTML4
8766 			if (settings.schema != "html5") {
8767 				each(split('strong/b em/i'), function(item) {
8768 					item = split(item, '/');
8769 					elements[item[1]].outputName = item[0];
8770 				});
8771 			}
8772 
8773 			// Add default alt attribute for images
8774 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
8775 
8776 			// Remove these if they are empty by default
8777 			each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
8778 				if (elements[name]) {
8779 					elements[name].removeEmpty = true;
8780 				}
8781 			});
8782 
8783 			// Padd these by default
8784 			each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
8785 				elements[name].paddEmpty = true;
8786 			});
8787 
8788 			// Remove these if they have no attributes
8789 			each(split('span'), function(name) {
8790 				elements[name].removeEmptyAttrs = true;
8791 			});
8792 
8793 			// Remove these by default
8794 			// TODO: Reenable in 4.1
8795 			/*each(split('script style'), function(name) {
8796 				delete elements[name];
8797 			});*/
8798 		} else {
8799 			setValidElements(settings.valid_elements);
8800 		}
8801 
8802 		addCustomElements(settings.custom_elements);
8803 		addValidChildren(settings.valid_children);
8804 		addValidElements(settings.extended_valid_elements);
8805 
8806 		// Todo: Remove this when we fix list handling to be valid
8807 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
8808 
8809 		// Delete invalid elements
8810 		if (settings.invalid_elements) {
8811 			each(explode(settings.invalid_elements), function(item) {
8812 				if (elements[item]) {
8813 					delete elements[item];
8814 				}
8815 			});
8816 		}
8817 
8818 		// If the user didn't allow span only allow internal spans
8819 		if (!getElementRule('span')) {
8820 			addValidElements('span[!data-mce-type|*]');
8821 		}
8822 
8823 		/**
8824 		 * Name/value map object with valid parents and children to those parents.
8825 		 *
8826 		 * @example
8827 		 * children = {
8828 		 *    div:{p:{}, h1:{}}
8829 		 * };
8830 		 * @field children
8831 		 * @type Object
8832 		 */
8833 		self.children = children;
8834 
8835 		/**
8836 		 * Name/value map object with valid styles for each element.
8837 		 *
8838 		 * @method getValidStyles
8839 		 * @type Object
8840 		 */
8841 		self.getValidStyles = function() {
8842 			return validStyles;
8843 		};
8844 
8845 		/**
8846 		 * Name/value map object with valid styles for each element.
8847 		 *
8848 		 * @method getInvalidStyles
8849 		 * @type Object
8850 		 */
8851 		self.getInvalidStyles = function() {
8852 			return invalidStyles;
8853 		};
8854 
8855 		/**
8856 		 * Name/value map object with valid classes for each element.
8857 		 *
8858 		 * @method getValidClasses
8859 		 * @type Object
8860 		 */
8861 		self.getValidClasses = function() {
8862 			return validClasses;
8863 		};
8864 
8865 		/**
8866 		 * Returns a map with boolean attributes.
8867 		 *
8868 		 * @method getBoolAttrs
8869 		 * @return {Object} Name/value lookup map for boolean attributes.
8870 		 */
8871 		self.getBoolAttrs = function() {
8872 			return boolAttrMap;
8873 		};
8874 
8875 		/**
8876 		 * Returns a map with block elements.
8877 		 *
8878 		 * @method getBlockElements
8879 		 * @return {Object} Name/value lookup map for block elements.
8880 		 */
8881 		self.getBlockElements = function() {
8882 			return blockElementsMap;
8883 		};
8884 
8885 		/**
8886 		 * Returns a map with text block elements. Such as: p,h1-h6,div,address
8887 		 *
8888 		 * @method getTextBlockElements
8889 		 * @return {Object} Name/value lookup map for block elements.
8890 		 */
8891 		self.getTextBlockElements = function() {
8892 			return textBlockElementsMap;
8893 		};
8894 
8895 		/**
8896 		 * Returns a map of inline text format nodes for example strong/span or ins.
8897 		 *
8898 		 * @method getTextInlineElements
8899 		 * @return {Object} Name/value lookup map for text format elements.
8900 		 */
8901 		self.getTextInlineElements = function() {
8902 			return textInlineElementsMap;
8903 		};
8904 
8905 		/**
8906 		 * Returns a map with short ended elements such as BR or IMG.
8907 		 *
8908 		 * @method getShortEndedElements
8909 		 * @return {Object} Name/value lookup map for short ended elements.
8910 		 */
8911 		self.getShortEndedElements = function() {
8912 			return shortEndedElementsMap;
8913 		};
8914 
8915 		/**
8916 		 * Returns a map with self closing tags such as <li>.
8917 		 *
8918 		 * @method getSelfClosingElements
8919 		 * @return {Object} Name/value lookup map for self closing tags elements.
8920 		 */
8921 		self.getSelfClosingElements = function() {
8922 			return selfClosingElementsMap;
8923 		};
8924 
8925 		/**
8926 		 * Returns a map with elements that should be treated as contents regardless if it has text
8927 		 * content in them or not such as TD, VIDEO or IMG.
8928 		 *
8929 		 * @method getNonEmptyElements
8930 		 * @return {Object} Name/value lookup map for non empty elements.
8931 		 */
8932 		self.getNonEmptyElements = function() {
8933 			return nonEmptyElementsMap;
8934 		};
8935 
8936 		/**
8937 		 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
8938 		 *
8939 		 * @method getWhiteSpaceElements
8940 		 * @return {Object} Name/value lookup map for white space elements.
8941 		 */
8942 		self.getWhiteSpaceElements = function() {
8943 			return whiteSpaceElementsMap;
8944 		};
8945 
8946 		/**
8947 		 * Returns a map with special elements. These are elements that needs to be parsed
8948 		 * in a special way such as script, style, textarea etc. The map object values
8949 		 * are regexps used to find the end of the element.
8950 		 *
8951 		 * @method getSpecialElements
8952 		 * @return {Object} Name/value lookup map for special elements.
8953 		 */
8954 		self.getSpecialElements = function() {
8955 			return specialElements;
8956 		};
8957 
8958 		/**
8959 		 * Returns true/false if the specified element and it's child is valid or not
8960 		 * according to the schema.
8961 		 *
8962 		 * @method isValidChild
8963 		 * @param {String} name Element name to check for.
8964 		 * @param {String} child Element child to verify.
8965 		 * @return {Boolean} True/false if the element is a valid child of the specified parent.
8966 		 */
8967 		self.isValidChild = function(name, child) {
8968 			var parent = children[name];
8969 
8970 			return !!(parent && parent[child]);
8971 		};
8972 
8973 		/**
8974 		 * Returns true/false if the specified element name and optional attribute is
8975 		 * valid according to the schema.
8976 		 *
8977 		 * @method isValid
8978 		 * @param {String} name Name of element to check.
8979 		 * @param {String} attr Optional attribute name to check for.
8980 		 * @return {Boolean} True/false if the element and attribute is valid.
8981 		 */
8982 		self.isValid = function(name, attr) {
8983 			var attrPatterns, i, rule = getElementRule(name);
8984 
8985 			// Check if it's a valid element
8986 			if (rule) {
8987 				if (attr) {
8988 					// Check if attribute name exists
8989 					if (rule.attributes[attr]) {
8990 						return true;
8991 					}
8992 
8993 					// Check if attribute matches a regexp pattern
8994 					attrPatterns = rule.attributePatterns;
8995 					if (attrPatterns) {
8996 						i = attrPatterns.length;
8997 						while (i--) {
8998 							if (attrPatterns[i].pattern.test(name)) {
8999 								return true;
9000 							}
9001 						}
9002 					}
9003 				} else {
9004 					return true;
9005 				}
9006 			}
9007 
9008 			// No match
9009 			return false;
9010 		};
9011 
9012 		/**
9013 		 * Returns true/false if the specified element is valid or not
9014 		 * according to the schema.
9015 		 *
9016 		 * @method getElementRule
9017 		 * @param {String} name Element name to check for.
9018 		 * @return {Object} Element object or undefined if the element isn't valid.
9019 		 */
9020 		self.getElementRule = getElementRule;
9021 
9022 		/**
9023 		 * Returns an map object of all custom elements.
9024 		 *
9025 		 * @method getCustomElements
9026 		 * @return {Object} Name/value map object of all custom elements.
9027 		 */
9028 		self.getCustomElements = function() {
9029 			return customElementsMap;
9030 		};
9031 
9032 		/**
9033 		 * Parses a valid elements string and adds it to the schema. The valid elements
9034 		 * format is for example "element[attr=default|otherattr]".
9035 		 * Existing rules will be replaced with the ones specified, so this extends the schema.
9036 		 *
9037 		 * @method addValidElements
9038 		 * @param {String} valid_elements String in the valid elements format to be parsed.
9039 		 */
9040 		self.addValidElements = addValidElements;
9041 
9042 		/**
9043 		 * Parses a valid elements string and sets it to the schema. The valid elements
9044 		 * format is for example "element[attr=default|otherattr]".
9045 		 * Existing rules will be replaced with the ones specified, so this extends the schema.
9046 		 *
9047 		 * @method setValidElements
9048 		 * @param {String} valid_elements String in the valid elements format to be parsed.
9049 		 */
9050 		self.setValidElements = setValidElements;
9051 
9052 		/**
9053 		 * Adds custom non HTML elements to the schema.
9054 		 *
9055 		 * @method addCustomElements
9056 		 * @param {String} custom_elements Comma separated list of custom elements to add.
9057 		 */
9058 		self.addCustomElements = addCustomElements;
9059 
9060 		/**
9061 		 * Parses a valid children string and adds them to the schema structure. The valid children
9062 		 * format is for example: "element[child1|child2]".
9063 		 *
9064 		 * @method addValidChildren
9065 		 * @param {String} valid_children Valid children elements string to parse
9066 		 */
9067 		self.addValidChildren = addValidChildren;
9068 
9069 		self.elements = elements;
9070 	};
9071 });
9072 
9073 // Included from: js/tinymce/classes/html/SaxParser.js
9074 
9075 /**
9076  * SaxParser.js
9077  *
9078  * Copyright, Moxiecode Systems AB
9079  * Released under LGPL License.
9080  *
9081  * License: http://www.tinymce.com/license
9082  * Contributing: http://www.tinymce.com/contributing
9083  */
9084 
9085 /*eslint max-depth:[2, 9] */
9086 
9087 /**
9088  * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
9089  * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
9090  * and attributes that doesn't fit the schema if the validate setting is enabled.
9091  *
9092  * @example
9093  * var parser = new tinymce.html.SaxParser({
9094  *     validate: true,
9095  *
9096  *     comment: function(text) {
9097  *         console.log('Comment:', text);
9098  *     },
9099  *
9100  *     cdata: function(text) {
9101  *         console.log('CDATA:', text);
9102  *     },
9103  *
9104  *     text: function(text, raw) {
9105  *         console.log('Text:', text, 'Raw:', raw);
9106  *     },
9107  *
9108  *     start: function(name, attrs, empty) {
9109  *         console.log('Start:', name, attrs, empty);
9110  *     },
9111  *
9112  *     end: function(name) {
9113  *         console.log('End:', name);
9114  *     },
9115  *
9116  *     pi: function(name, text) {
9117  *         console.log('PI:', name, text);
9118  *     },
9119  *
9120  *     doctype: function(text) {
9121  *         console.log('DocType:', text);
9122  *     }
9123  * }, schema);
9124  * @class tinymce.html.SaxParser
9125  * @version 3.4
9126  */
9127 define("tinymce/html/SaxParser", [
9128 	"tinymce/html/Schema",
9129 	"tinymce/html/Entities",
9130 	"tinymce/util/Tools"
9131 ], function(Schema, Entities, Tools) {
9132 	var each = Tools.each;
9133 
9134 	/**
9135 	 * Returns the index of the end tag for a specific start tag. This can be
9136 	 * used to skip all children of a parent element from being processed.
9137 	 *
9138 	 * @private
9139 	 * @method findEndTag
9140 	 * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
9141 	 * @param {String} html HTML string to find the end tag in.
9142 	 * @param {Number} startIndex Indext to start searching at should be after the start tag.
9143 	 * @return {Number} Index of the end tag.
9144 	 */
9145 	function findEndTag(schema, html, startIndex) {
9146 		var count = 1, index, matches, tokenRegExp, shortEndedElements;
9147 
9148 		shortEndedElements = schema.getShortEndedElements();
9149 		tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
9150 		tokenRegExp.lastIndex = index = startIndex;
9151 
9152 		while ((matches = tokenRegExp.exec(html))) {
9153 			index = tokenRegExp.lastIndex;
9154 
9155 			if (matches[1] === '/') { // End element
9156 				count--;
9157 			} else if (!matches[1]) { // Start element
9158 				if (matches[2] in shortEndedElements) {
9159 					continue;
9160 				}
9161 
9162 				count++;
9163 			}
9164 
9165 			if (count === 0) {
9166 				break;
9167 			}
9168 		}
9169 
9170 		return index;
9171 	}
9172 
9173 	/**
9174 	 * Constructs a new SaxParser instance.
9175 	 *
9176 	 * @constructor
9177 	 * @method SaxParser
9178 	 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
9179 	 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
9180 	 */
9181 	function SaxParser(settings, schema) {
9182 		var self = this;
9183 
9184 		function noop() {}
9185 
9186 		settings = settings || {};
9187 		self.schema = schema = schema || new Schema();
9188 
9189 		if (settings.fix_self_closing !== false) {
9190 			settings.fix_self_closing = true;
9191 		}
9192 
9193 		// Add handler functions from settings and setup default handlers
9194 		each('comment cdata text start end pi doctype'.split(' '), function(name) {
9195 			if (name) {
9196 				self[name] = settings[name] || noop;
9197 			}
9198 		});
9199 
9200 		/**
9201 		 * Parses the specified HTML string and executes the callbacks for each item it finds.
9202 		 *
9203 		 * @example
9204 		 * new SaxParser({...}).parse('<b>text</b>');
9205 		 * @method parse
9206 		 * @param {String} html Html string to sax parse.
9207 		 */
9208 		self.parse = function(html) {
9209 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
9210 			var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
9211 			var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
9212 			var attributesRequired, attributesDefault, attributesForced;
9213 			var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
9214 			var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
9215 			var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
9216 
9217 			function processEndTag(name) {
9218 				var pos, i;
9219 
9220 				// Find position of parent of the same type
9221 				pos = stack.length;
9222 				while (pos--) {
9223 					if (stack[pos].name === name) {
9224 						break;
9225 					}
9226 				}
9227 
9228 				// Found parent
9229 				if (pos >= 0) {
9230 					// Close all the open elements
9231 					for (i = stack.length - 1; i >= pos; i--) {
9232 						name = stack[i];
9233 
9234 						if (name.valid) {
9235 							self.end(name.name);
9236 						}
9237 					}
9238 
9239 					// Remove the open elements from the stack
9240 					stack.length = pos;
9241 				}
9242 			}
9243 
9244 			function parseAttribute(match, name, value, val2, val3) {
9245 				var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
9246 
9247 				name = name.toLowerCase();
9248 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
9249 
9250 				// Validate name and value pass through all data- attributes
9251 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
9252 					attrRule = validAttributesMap[name];
9253 
9254 					// Find rule by pattern matching
9255 					if (!attrRule && validAttributePatterns) {
9256 						i = validAttributePatterns.length;
9257 						while (i--) {
9258 							attrRule = validAttributePatterns[i];
9259 							if (attrRule.pattern.test(name)) {
9260 								break;
9261 							}
9262 						}
9263 
9264 						// No rule matched
9265 						if (i === -1) {
9266 							attrRule = null;
9267 						}
9268 					}
9269 
9270 					// No attribute rule found
9271 					if (!attrRule) {
9272 						return;
9273 					}
9274 
9275 					// Validate value
9276 					if (attrRule.validValues && !(value in attrRule.validValues)) {
9277 						return;
9278 					}
9279 				}
9280 
9281 				// Block any javascript: urls or non image data uris
9282 				if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
9283 					var uri = value.replace(trimRegExp, '');
9284 
9285 					try {
9286 						// Might throw malformed URI sequence
9287 						uri = decodeURIComponent(uri);
9288 					} catch (ex) {
9289 						// Fallback to non UTF-8 decoder
9290 						uri = unescape(uri);
9291 					}
9292 
9293 					if (scriptUriRegExp.test(uri)) {
9294 						return;
9295 					}
9296 
9297 					if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
9298 						return;
9299 					}
9300 				}
9301 
9302 				// Add attribute to list and map
9303 				attrList.map[name] = value;
9304 				attrList.push({
9305 					name: name,
9306 					value: value
9307 				});
9308 			}
9309 
9310 			// Precompile RegExps and map objects
9311 			tokenRegExp = new RegExp('<(?:' +
9312 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
9313 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
9314 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
9315 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
9316 				'(?:\\/([^>]+)>)|' + // End element
9317 				'(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
9318 			')', 'g');
9319 
9320 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
9321 
9322 			// Setup lookup tables for empty elements and boolean attributes
9323 			shortEndedElements = schema.getShortEndedElements();
9324 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
9325 			fillAttrsMap = schema.getBoolAttrs();
9326 			validate = settings.validate;
9327 			removeInternalElements = settings.remove_internals;
9328 			fixSelfClosing = settings.fix_self_closing;
9329 			specialElements = schema.getSpecialElements();
9330 
9331 			while ((matches = tokenRegExp.exec(html))) {
9332 				// Text
9333 				if (index < matches.index) {
9334 					self.text(decode(html.substr(index, matches.index - index)));
9335 				}
9336 
9337 				if ((value = matches[6])) { // End element
9338 					value = value.toLowerCase();
9339 
9340 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
9341 					if (value.charAt(0) === ':') {
9342 						value = value.substr(1);
9343 					}
9344 
9345 					processEndTag(value);
9346 				} else if ((value = matches[7])) { // Start element
9347 					value = value.toLowerCase();
9348 
9349 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
9350 					if (value.charAt(0) === ':') {
9351 						value = value.substr(1);
9352 					}
9353 
9354 					isShortEnded = value in shortEndedElements;
9355 
9356 					// Is self closing tag for example an <li> after an open <li>
9357 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
9358 						processEndTag(value);
9359 					}
9360 
9361 					// Validate element
9362 					if (!validate || (elementRule = schema.getElementRule(value))) {
9363 						isValidElement = true;
9364 
9365 						// Grab attributes map and patters when validation is enabled
9366 						if (validate) {
9367 							validAttributesMap = elementRule.attributes;
9368 							validAttributePatterns = elementRule.attributePatterns;
9369 						}
9370 
9371 						// Parse attributes
9372 						if ((attribsValue = matches[8])) {
9373 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
9374 
9375 							// If the element has internal attributes then remove it if we are told to do so
9376 							if (isInternalElement && removeInternalElements) {
9377 								isValidElement = false;
9378 							}
9379 
9380 							attrList = [];
9381 							attrList.map = {};
9382 
9383 							attribsValue.replace(attrRegExp, parseAttribute);
9384 						} else {
9385 							attrList = [];
9386 							attrList.map = {};
9387 						}
9388 
9389 						// Process attributes if validation is enabled
9390 						if (validate && !isInternalElement) {
9391 							attributesRequired = elementRule.attributesRequired;
9392 							attributesDefault = elementRule.attributesDefault;
9393 							attributesForced = elementRule.attributesForced;
9394 							anyAttributesRequired = elementRule.removeEmptyAttrs;
9395 
9396 							// Check if any attribute exists
9397 							if (anyAttributesRequired && !attrList.length) {
9398 								isValidElement = false;
9399 							}
9400 
9401 							// Handle forced attributes
9402 							if (attributesForced) {
9403 								i = attributesForced.length;
9404 								while (i--) {
9405 									attr = attributesForced[i];
9406 									name = attr.name;
9407 									attrValue = attr.value;
9408 
9409 									if (attrValue === '{$uid}') {
9410 										attrValue = 'mce_' + idCount++;
9411 									}
9412 
9413 									attrList.map[name] = attrValue;
9414 									attrList.push({name: name, value: attrValue});
9415 								}
9416 							}
9417 
9418 							// Handle default attributes
9419 							if (attributesDefault) {
9420 								i = attributesDefault.length;
9421 								while (i--) {
9422 									attr = attributesDefault[i];
9423 									name = attr.name;
9424 
9425 									if (!(name in attrList.map)) {
9426 										attrValue = attr.value;
9427 
9428 										if (attrValue === '{$uid}') {
9429 											attrValue = 'mce_' + idCount++;
9430 										}
9431 
9432 										attrList.map[name] = attrValue;
9433 										attrList.push({name: name, value: attrValue});
9434 									}
9435 								}
9436 							}
9437 
9438 							// Handle required attributes
9439 							if (attributesRequired) {
9440 								i = attributesRequired.length;
9441 								while (i--) {
9442 									if (attributesRequired[i] in attrList.map) {
9443 										break;
9444 									}
9445 								}
9446 
9447 								// None of the required attributes where found
9448 								if (i === -1) {
9449 									isValidElement = false;
9450 								}
9451 							}
9452 
9453 							// Invalidate element if it's marked as bogus
9454 							if ((attr = attrList.map['data-mce-bogus'])) {
9455 								if (attr === 'all') {
9456 									index = findEndTag(schema, html, tokenRegExp.lastIndex);
9457 									tokenRegExp.lastIndex = index;
9458 									continue;
9459 								}
9460 
9461 								isValidElement = false;
9462 							}
9463 						}
9464 
9465 						if (isValidElement) {
9466 							self.start(value, attrList, isShortEnded);
9467 						}
9468 					} else {
9469 						isValidElement = false;
9470 					}
9471 
9472 					// Treat script, noscript and style a bit different since they may include code that looks like elements
9473 					if ((endRegExp = specialElements[value])) {
9474 						endRegExp.lastIndex = index = matches.index + matches[0].length;
9475 
9476 						if ((matches = endRegExp.exec(html))) {
9477 							if (isValidElement) {
9478 								text = html.substr(index, matches.index - index);
9479 							}
9480 
9481 							index = matches.index + matches[0].length;
9482 						} else {
9483 							text = html.substr(index);
9484 							index = html.length;
9485 						}
9486 
9487 						if (isValidElement) {
9488 							if (text.length > 0) {
9489 								self.text(text, true);
9490 							}
9491 
9492 							self.end(value);
9493 						}
9494 
9495 						tokenRegExp.lastIndex = index;
9496 						continue;
9497 					}
9498 
9499 					// Push value on to stack
9500 					if (!isShortEnded) {
9501 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
9502 							stack.push({name: value, valid: isValidElement});
9503 						} else if (isValidElement) {
9504 							self.end(value);
9505 						}
9506 					}
9507 				} else if ((value = matches[1])) { // Comment
9508 					// Padd comment value to avoid browsers from parsing invalid comments as HTML
9509 					if (value.charAt(0) === '>') {
9510 						value = ' ' + value;
9511 					}
9512 
9513 					if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
9514 						value = ' ' + value;
9515 					}
9516 
9517 					self.comment(value);
9518 				} else if ((value = matches[2])) { // CDATA
9519 					self.cdata(value);
9520 				} else if ((value = matches[3])) { // DOCTYPE
9521 					self.doctype(value);
9522 				} else if ((value = matches[4])) { // PI
9523 					self.pi(value, matches[5]);
9524 				}
9525 
9526 				index = matches.index + matches[0].length;
9527 			}
9528 
9529 			// Text
9530 			if (index < html.length) {
9531 				self.text(decode(html.substr(index)));
9532 			}
9533 
9534 			// Close any open elements
9535 			for (i = stack.length - 1; i >= 0; i--) {
9536 				value = stack[i];
9537 
9538 				if (value.valid) {
9539 					self.end(value.name);
9540 				}
9541 			}
9542 		};
9543 	}
9544 
9545 	SaxParser.findEndTag = findEndTag;
9546 
9547 	return SaxParser;
9548 });
9549 
9550 // Included from: js/tinymce/classes/html/DomParser.js
9551 
9552 /**
9553  * DomParser.js
9554  *
9555  * Copyright, Moxiecode Systems AB
9556  * Released under LGPL License.
9557  *
9558  * License: http://www.tinymce.com/license
9559  * Contributing: http://www.tinymce.com/contributing
9560  */
9561 
9562 /**
9563  * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
9564  * sure that the node tree is valid according to the specified schema.
9565  * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
9566  *
9567  * @example
9568  * var parser = new tinymce.html.DomParser({validate: true}, schema);
9569  * var rootNode = parser.parse('<h1>content</h1>');
9570  *
9571  * @class tinymce.html.DomParser
9572  * @version 3.4
9573  */
9574 define("tinymce/html/DomParser", [
9575 	"tinymce/html/Node",
9576 	"tinymce/html/Schema",
9577 	"tinymce/html/SaxParser",
9578 	"tinymce/util/Tools"
9579 ], function(Node, Schema, SaxParser, Tools) {
9580 	var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
9581 
9582 	/**
9583 	 * Constructs a new DomParser instance.
9584 	 *
9585 	 * @constructor
9586 	 * @method DomParser
9587 	 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
9588 	 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
9589 	 */
9590 	return function(settings, schema) {
9591 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
9592 
9593 		settings = settings || {};
9594 		settings.validate = "validate" in settings ? settings.validate : true;
9595 		settings.root_name = settings.root_name || 'body';
9596 		self.schema = schema = schema || new Schema();
9597 
9598 		function fixInvalidChildren(nodes) {
9599 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
9600 			var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
9601 
9602 			nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
9603 			nonEmptyElements = schema.getNonEmptyElements();
9604 			textBlockElements = schema.getTextBlockElements();
9605 
9606 			for (ni = 0; ni < nodes.length; ni++) {
9607 				node = nodes[ni];
9608 
9609 				// Already removed or fixed
9610 				if (!node.parent || node.fixed) {
9611 					continue;
9612 				}
9613 
9614 				// If the invalid element is a text block and the text block is within a parent LI element
9615 				// Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
9616 				if (textBlockElements[node.name] && node.parent.name == 'li') {
9617 					// Move sibling text blocks after LI element
9618 					sibling = node.next;
9619 					while (sibling) {
9620 						if (textBlockElements[sibling.name]) {
9621 							sibling.name = 'li';
9622 							sibling.fixed = true;
9623 							node.parent.insert(sibling, node.parent);
9624 						} else {
9625 							break;
9626 						}
9627 
9628 						sibling = sibling.next;
9629 					}
9630 
9631 					// Unwrap current text block
9632 					node.unwrap(node);
9633 					continue;
9634 				}
9635 
9636 				// Get list of all parent nodes until we find a valid parent to stick the child into
9637 				parents = [node];
9638 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
9639 					!nonSplitableElements[parent.name]; parent = parent.parent) {
9640 					parents.push(parent);
9641 				}
9642 
9643 				// Found a suitable parent
9644 				if (parent && parents.length > 1) {
9645 					// Reverse the array since it makes looping easier
9646 					parents.reverse();
9647 
9648 					// Clone the related parent and insert that after the moved node
9649 					newParent = currentNode = self.filterNode(parents[0].clone());
9650 
9651 					// Start cloning and moving children on the left side of the target node
9652 					for (i = 0; i < parents.length - 1; i++) {
9653 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
9654 							tempNode = self.filterNode(parents[i].clone());
9655 							currentNode.append(tempNode);
9656 						} else {
9657 							tempNode = currentNode;
9658 						}
9659 
9660 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
9661 							nextNode = childNode.next;
9662 							tempNode.append(childNode);
9663 							childNode = nextNode;
9664 						}
9665 
9666 						currentNode = tempNode;
9667 					}
9668 
9669 					if (!newParent.isEmpty(nonEmptyElements)) {
9670 						parent.insert(newParent, parents[0], true);
9671 						parent.insert(node, newParent);
9672 					} else {
9673 						parent.insert(node, parents[0], true);
9674 					}
9675 
9676 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
9677 					parent = parents[0];
9678 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
9679 						parent.empty().remove();
9680 					}
9681 				} else if (node.parent) {
9682 					// If it's an LI try to find a UL/OL for it or wrap it
9683 					if (node.name === 'li') {
9684 						sibling = node.prev;
9685 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
9686 							sibling.append(node);
9687 							continue;
9688 						}
9689 
9690 						sibling = node.next;
9691 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
9692 							sibling.insert(node, sibling.firstChild, true);
9693 							continue;
9694 						}
9695 
9696 						node.wrap(self.filterNode(new Node('ul', 1)));
9697 						continue;
9698 					}
9699 
9700 					// Try wrapping the element in a DIV
9701 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
9702 						node.wrap(self.filterNode(new Node('div', 1)));
9703 					} else {
9704 						// We failed wrapping it, then remove or unwrap it
9705 						if (node.name === 'style' || node.name === 'script') {
9706 							node.empty().remove();
9707 						} else {
9708 							node.unwrap();
9709 						}
9710 					}
9711 				}
9712 			}
9713 		}
9714 
9715 		/**
9716 		 * Runs the specified node though the element and attributes filters.
9717 		 *
9718 		 * @method filterNode
9719 		 * @param {tinymce.html.Node} Node the node to run filters on.
9720 		 * @return {tinymce.html.Node} The passed in node.
9721 		 */
9722 		self.filterNode = function(node) {
9723 			var i, name, list;
9724 
9725 			// Run element filters
9726 			if (name in nodeFilters) {
9727 				list = matchedNodes[name];
9728 
9729 				if (list) {
9730 					list.push(node);
9731 				} else {
9732 					matchedNodes[name] = [node];
9733 				}
9734 			}
9735 
9736 			// Run attribute filters
9737 			i = attributeFilters.length;
9738 			while (i--) {
9739 				name = attributeFilters[i].name;
9740 
9741 				if (name in node.attributes.map) {
9742 					list = matchedAttributes[name];
9743 
9744 					if (list) {
9745 						list.push(node);
9746 					} else {
9747 						matchedAttributes[name] = [node];
9748 					}
9749 				}
9750 			}
9751 
9752 			return node;
9753 		};
9754 
9755 		/**
9756 		 * Adds a node filter function to the parser, the parser will collect the specified nodes by name
9757 		 * and then execute the callback ones it has finished parsing the document.
9758 		 *
9759 		 * @example
9760 		 * parser.addNodeFilter('p,h1', function(nodes, name) {
9761 		 *		for (var i = 0; i < nodes.length; i++) {
9762 		 *			console.log(nodes[i].name);
9763 		 *		}
9764 		 * });
9765 		 * @method addNodeFilter
9766 		 * @method {String} name Comma separated list of nodes to collect.
9767 		 * @param {function} callback Callback function to execute once it has collected nodes.
9768 		 */
9769 		self.addNodeFilter = function(name, callback) {
9770 			each(explode(name), function(name) {
9771 				var list = nodeFilters[name];
9772 
9773 				if (!list) {
9774 					nodeFilters[name] = list = [];
9775 				}
9776 
9777 				list.push(callback);
9778 			});
9779 		};
9780 
9781 		/**
9782 		 * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
9783 		 * and then execute the callback ones it has finished parsing the document.
9784 		 *
9785 		 * @example
9786 		 * parser.addAttributeFilter('src,href', function(nodes, name) {
9787 		 *		for (var i = 0; i < nodes.length; i++) {
9788 		 *			console.log(nodes[i].name);
9789 		 *		}
9790 		 * });
9791 		 * @method addAttributeFilter
9792 		 * @method {String} name Comma separated list of nodes to collect.
9793 		 * @param {function} callback Callback function to execute once it has collected nodes.
9794 		 */
9795 		self.addAttributeFilter = function(name, callback) {
9796 			each(explode(name), function(name) {
9797 				var i;
9798 
9799 				for (i = 0; i < attributeFilters.length; i++) {
9800 					if (attributeFilters[i].name === name) {
9801 						attributeFilters[i].callbacks.push(callback);
9802 						return;
9803 					}
9804 				}
9805 
9806 				attributeFilters.push({name: name, callbacks: [callback]});
9807 			});
9808 		};
9809 
9810 		/**
9811 		 * Parses the specified HTML string into a DOM like node tree and returns the result.
9812 		 *
9813 		 * @example
9814 		 * var rootNode = new DomParser({...}).parse('<b>text</b>');
9815 		 * @method parse
9816 		 * @param {String} html Html string to sax parse.
9817 		 * @param {Object} args Optional args object that gets passed to all filter functions.
9818 		 * @return {tinymce.html.Node} Root node containing the tree.
9819 		 */
9820 		self.parse = function(html, args) {
9821 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
9822 			var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
9823 			var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
9824 			var children, nonEmptyElements, rootBlockName;
9825 
9826 			args = args || {};
9827 			matchedNodes = {};
9828 			matchedAttributes = {};
9829 			blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
9830 			nonEmptyElements = schema.getNonEmptyElements();
9831 			children = schema.children;
9832 			validate = settings.validate;
9833 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
9834 
9835 			whiteSpaceElements = schema.getWhiteSpaceElements();
9836 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
9837 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
9838 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
9839 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
9840 
9841 			function addRootBlocks() {
9842 				var node = rootNode.firstChild, next, rootBlockNode;
9843 
9844 				// Removes whitespace at beginning and end of block so:
9845 				// <p> x </p> -> <p>x</p>
9846 				function trim(rootBlockNode) {
9847 					if (rootBlockNode) {
9848 						node = rootBlockNode.firstChild;
9849 						if (node && node.type == 3) {
9850 							node.value = node.value.replace(startWhiteSpaceRegExp, '');
9851 						}
9852 
9853 						node = rootBlockNode.lastChild;
9854 						if (node && node.type == 3) {
9855 							node.value = node.value.replace(endWhiteSpaceRegExp, '');
9856 						}
9857 					}
9858 				}
9859 
9860 				// Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
9861 				if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
9862 					return;
9863 				}
9864 
9865 				while (node) {
9866 					next = node.next;
9867 
9868 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
9869 						!blockElements[node.name] && !node.attr('data-mce-type'))) {
9870 						if (!rootBlockNode) {
9871 							// Create a new root block element
9872 							rootBlockNode = createNode(rootBlockName, 1);
9873 							rootBlockNode.attr(settings.forced_root_block_attrs);
9874 							rootNode.insert(rootBlockNode, node);
9875 							rootBlockNode.append(node);
9876 						} else {
9877 							rootBlockNode.append(node);
9878 						}
9879 					} else {
9880 						trim(rootBlockNode);
9881 						rootBlockNode = null;
9882 					}
9883 
9884 					node = next;
9885 				}
9886 
9887 				trim(rootBlockNode);
9888 			}
9889 
9890 			function createNode(name, type) {
9891 				var node = new Node(name, type), list;
9892 
9893 				if (name in nodeFilters) {
9894 					list = matchedNodes[name];
9895 
9896 					if (list) {
9897 						list.push(node);
9898 					} else {
9899 						matchedNodes[name] = [node];
9900 					}
9901 				}
9902 
9903 				return node;
9904 			}
9905 
9906 			function removeWhitespaceBefore(node) {
9907 				var textNode, textVal, sibling;
9908 
9909 				for (textNode = node.prev; textNode && textNode.type === 3;) {
9910 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
9911 
9912 					if (textVal.length > 0) {
9913 						textNode.value = textVal;
9914 						textNode = textNode.prev;
9915 					} else {
9916 						sibling = textNode.prev;
9917 						textNode.remove();
9918 						textNode = sibling;
9919 					}
9920 				}
9921 			}
9922 
9923 			function cloneAndExcludeBlocks(input) {
9924 				var name, output = {};
9925 
9926 				for (name in input) {
9927 					if (name !== 'li' && name != 'p') {
9928 						output[name] = input[name];
9929 					}
9930 				}
9931 
9932 				return output;
9933 			}
9934 
9935 			parser = new SaxParser({
9936 				validate: validate,
9937 				allow_script_urls: settings.allow_script_urls,
9938 				allow_conditional_comments: settings.allow_conditional_comments,
9939 
9940 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
9941 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
9942 
9943 				cdata: function(text) {
9944 					node.append(createNode('#cdata', 4)).value = text;
9945 				},
9946 
9947 				text: function(text, raw) {
9948 					var textNode;
9949 
9950 					// Trim all redundant whitespace on non white space elements
9951 					if (!isInWhiteSpacePreservedElement) {
9952 						text = text.replace(allWhiteSpaceRegExp, ' ');
9953 
9954 						if (node.lastChild && blockElements[node.lastChild.name]) {
9955 							text = text.replace(startWhiteSpaceRegExp, '');
9956 						}
9957 					}
9958 
9959 					// Do we need to create the node
9960 					if (text.length !== 0) {
9961 						textNode = createNode('#text', 3);
9962 						textNode.raw = !!raw;
9963 						node.append(textNode).value = text;
9964 					}
9965 				},
9966 
9967 				comment: function(text) {
9968 					node.append(createNode('#comment', 8)).value = text;
9969 				},
9970 
9971 				pi: function(name, text) {
9972 					node.append(createNode(name, 7)).value = text;
9973 					removeWhitespaceBefore(node);
9974 				},
9975 
9976 				doctype: function(text) {
9977 					var newNode;
9978 
9979 					newNode = node.append(createNode('#doctype', 10));
9980 					newNode.value = text;
9981 					removeWhitespaceBefore(node);
9982 				},
9983 
9984 				start: function(name, attrs, empty) {
9985 					var newNode, attrFiltersLen, elementRule, attrName, parent;
9986 
9987 					elementRule = validate ? schema.getElementRule(name) : {};
9988 					if (elementRule) {
9989 						newNode = createNode(elementRule.outputName || name, 1);
9990 						newNode.attributes = attrs;
9991 						newNode.shortEnded = empty;
9992 
9993 						node.append(newNode);
9994 
9995 						// Check if node is valid child of the parent node is the child is
9996 						// unknown we don't collect it since it's probably a custom element
9997 						parent = children[node.name];
9998 						if (parent && children[newNode.name] && !parent[newNode.name]) {
9999 							invalidChildren.push(newNode);
10000 						}
10001 
10002 						attrFiltersLen = attributeFilters.length;
10003 						while (attrFiltersLen--) {
10004 							attrName = attributeFilters[attrFiltersLen].name;
10005 
10006 							if (attrName in attrs.map) {
10007 								list = matchedAttributes[attrName];
10008 
10009 								if (list) {
10010 									list.push(newNode);
10011 								} else {
10012 									matchedAttributes[attrName] = [newNode];
10013 								}
10014 							}
10015 						}
10016 
10017 						// Trim whitespace before block
10018 						if (blockElements[name]) {
10019 							removeWhitespaceBefore(newNode);
10020 						}
10021 
10022 						// Change current node if the element wasn't empty i.e not <br /> or <img />
10023 						if (!empty) {
10024 							node = newNode;
10025 						}
10026 
10027 						// Check if we are inside a whitespace preserved element
10028 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
10029 							isInWhiteSpacePreservedElement = true;
10030 						}
10031 					}
10032 				},
10033 
10034 				end: function(name) {
10035 					var textNode, elementRule, text, sibling, tempNode;
10036 
10037 					elementRule = validate ? schema.getElementRule(name) : {};
10038 					if (elementRule) {
10039 						if (blockElements[name]) {
10040 							if (!isInWhiteSpacePreservedElement) {
10041 								// Trim whitespace of the first node in a block
10042 								textNode = node.firstChild;
10043 								if (textNode && textNode.type === 3) {
10044 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
10045 
10046 									// Any characters left after trim or should we remove it
10047 									if (text.length > 0) {
10048 										textNode.value = text;
10049 										textNode = textNode.next;
10050 									} else {
10051 										sibling = textNode.next;
10052 										textNode.remove();
10053 										textNode = sibling;
10054 
10055 										// Remove any pure whitespace siblings
10056 										while (textNode && textNode.type === 3) {
10057 											text = textNode.value;
10058 											sibling = textNode.next;
10059 
10060 											if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
10061 												textNode.remove();
10062 												textNode = sibling;
10063 											}
10064 
10065 											textNode = sibling;
10066 										}
10067 									}
10068 								}
10069 
10070 								// Trim whitespace of the last node in a block
10071 								textNode = node.lastChild;
10072 								if (textNode && textNode.type === 3) {
10073 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
10074 
10075 									// Any characters left after trim or should we remove it
10076 									if (text.length > 0) {
10077 										textNode.value = text;
10078 										textNode = textNode.prev;
10079 									} else {
10080 										sibling = textNode.prev;
10081 										textNode.remove();
10082 										textNode = sibling;
10083 
10084 										// Remove any pure whitespace siblings
10085 										while (textNode && textNode.type === 3) {
10086 											text = textNode.value;
10087 											sibling = textNode.prev;
10088 
10089 											if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
10090 												textNode.remove();
10091 												textNode = sibling;
10092 											}
10093 
10094 											textNode = sibling;
10095 										}
10096 									}
10097 								}
10098 							}
10099 
10100 							// Trim start white space
10101 							// Removed due to: #5424
10102 							/*textNode = node.prev;
10103 							if (textNode && textNode.type === 3) {
10104 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
10105 
10106 								if (text.length > 0)
10107 									textNode.value = text;
10108 								else
10109 									textNode.remove();
10110 							}*/
10111 						}
10112 
10113 						// Check if we exited a whitespace preserved element
10114 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
10115 							isInWhiteSpacePreservedElement = false;
10116 						}
10117 
10118 						// Handle empty nodes
10119 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
10120 							if (node.isEmpty(nonEmptyElements)) {
10121 								if (elementRule.paddEmpty) {
10122 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
10123 								} else {
10124 									// Leave nodes that have a name like <a name="name">
10125 									if (!node.attributes.map.name && !node.attributes.map.id) {
10126 										tempNode = node.parent;
10127 
10128 										if (blockElements[node.name]) {
10129 											node.empty().remove();
10130 										} else {
10131 											node.unwrap();
10132 										}
10133 
10134 										node = tempNode;
10135 										return;
10136 									}
10137 								}
10138 							}
10139 						}
10140 
10141 						node = node.parent;
10142 					}
10143 				}
10144 			}, schema);
10145 
10146 			rootNode = node = new Node(args.context || settings.root_name, 11);
10147 
10148 			parser.parse(html);
10149 
10150 			// Fix invalid children or report invalid children in a contextual parsing
10151 			if (validate && invalidChildren.length) {
10152 				if (!args.context) {
10153 					fixInvalidChildren(invalidChildren);
10154 				} else {
10155 					args.invalid = true;
10156 				}
10157 			}
10158 
10159 			// Wrap nodes in the root into block elements if the root is body
10160 			if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
10161 				addRootBlocks();
10162 			}
10163 
10164 			// Run filters only when the contents is valid
10165 			if (!args.invalid) {
10166 				// Run node filters
10167 				for (name in matchedNodes) {
10168 					list = nodeFilters[name];
10169 					nodes = matchedNodes[name];
10170 
10171 					// Remove already removed children
10172 					fi = nodes.length;
10173 					while (fi--) {
10174 						if (!nodes[fi].parent) {
10175 							nodes.splice(fi, 1);
10176 						}
10177 					}
10178 
10179 					for (i = 0, l = list.length; i < l; i++) {
10180 						list[i](nodes, name, args);
10181 					}
10182 				}
10183 
10184 				// Run attribute filters
10185 				for (i = 0, l = attributeFilters.length; i < l; i++) {
10186 					list = attributeFilters[i];
10187 
10188 					if (list.name in matchedAttributes) {
10189 						nodes = matchedAttributes[list.name];
10190 
10191 						// Remove already removed children
10192 						fi = nodes.length;
10193 						while (fi--) {
10194 							if (!nodes[fi].parent) {
10195 								nodes.splice(fi, 1);
10196 							}
10197 						}
10198 
10199 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
10200 							list.callbacks[fi](nodes, list.name, args);
10201 						}
10202 					}
10203 				}
10204 			}
10205 
10206 			return rootNode;
10207 		};
10208 
10209 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
10210 		// make it possible to place the caret inside empty blocks. This logic tries to remove
10211 		// these elements and keep br elements that where intended to be there intact
10212 		if (settings.remove_trailing_brs) {
10213 			self.addNodeFilter('br', function(nodes) {
10214 				var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
10215 				var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
10216 				var elementRule, textNode;
10217 
10218 				// Remove brs from body element as well
10219 				blockElements.body = 1;
10220 
10221 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
10222 				for (i = 0; i < l; i++) {
10223 					node = nodes[i];
10224 					parent = node.parent;
10225 
10226 					if (blockElements[node.parent.name] && node === parent.lastChild) {
10227 						// Loop all nodes to the left of the current node and check for other BR elements
10228 						// excluding bookmarks since they are invisible
10229 						prev = node.prev;
10230 						while (prev) {
10231 							prevName = prev.name;
10232 
10233 							// Ignore bookmarks
10234 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
10235 								// Found a non BR element
10236 								if (prevName !== "br") {
10237 									break;
10238 								}
10239 
10240 								// Found another br it's a <br><br> structure then don't remove anything
10241 								if (prevName === 'br') {
10242 									node = null;
10243 									break;
10244 								}
10245 							}
10246 
10247 							prev = prev.prev;
10248 						}
10249 
10250 						if (node) {
10251 							node.remove();
10252 
10253 							// Is the parent to be considered empty after we removed the BR
10254 							if (parent.isEmpty(nonEmptyElements)) {
10255 								elementRule = schema.getElementRule(parent.name);
10256 
10257 								// Remove or padd the element depending on schema rule
10258 								if (elementRule) {
10259 									if (elementRule.removeEmpty) {
10260 										parent.remove();
10261 									} else if (elementRule.paddEmpty) {
10262 										parent.empty().append(new Node('#text', 3)).value = '\u00a0';
10263 									}
10264 								}
10265 							}
10266 						}
10267 					} else {
10268 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
10269 						// so they become <p><b><i> </i></b></p>
10270 						lastParent = node;
10271 						while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
10272 							lastParent = parent;
10273 
10274 							if (blockElements[parent.name]) {
10275 								break;
10276 							}
10277 
10278 							parent = parent.parent;
10279 						}
10280 
10281 						if (lastParent === parent) {
10282 							textNode = new Node('#text', 3);
10283 							textNode.value = '\u00a0';
10284 							node.replace(textNode);
10285 						}
10286 					}
10287 				}
10288 			});
10289 		}
10290 
10291 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
10292 		if (!settings.allow_html_in_named_anchor) {
10293 			self.addAttributeFilter('id,name', function(nodes) {
10294 				var i = nodes.length, sibling, prevSibling, parent, node;
10295 
10296 				while (i--) {
10297 					node = nodes[i];
10298 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
10299 						parent = node.parent;
10300 
10301 						// Move children after current node
10302 						sibling = node.lastChild;
10303 						do {
10304 							prevSibling = sibling.prev;
10305 							parent.insert(sibling, node);
10306 							sibling = prevSibling;
10307 						} while (sibling);
10308 					}
10309 				}
10310 			});
10311 		}
10312 
10313 		if (settings.validate && schema.getValidClasses()) {
10314 			self.addAttributeFilter('class', function(nodes) {
10315 				var i = nodes.length, node, classList, ci, className, classValue;
10316 				var validClasses = schema.getValidClasses(), validClassesMap, valid;
10317 
10318 				while (i--) {
10319 					node = nodes[i];
10320 					classList = node.attr('class').split(' ');
10321 					classValue = '';
10322 
10323 					for (ci = 0; ci < classList.length; ci++) {
10324 						className = classList[ci];
10325 						valid = false;
10326 
10327 						validClassesMap = validClasses['*'];
10328 						if (validClassesMap && validClassesMap[className]) {
10329 							valid = true;
10330 						}
10331 
10332 						validClassesMap = validClasses[node.name];
10333 						if (!valid && validClassesMap && !validClassesMap[className]) {
10334 							valid = true;
10335 						}
10336 
10337 						if (valid) {
10338 							if (classValue) {
10339 								classValue += ' ';
10340 							}
10341 
10342 							classValue += className;
10343 						}
10344 					}
10345 
10346 					if (!classValue.length) {
10347 						classValue = null;
10348 					}
10349 
10350 					node.attr('class', classValue);
10351 				}
10352 			});
10353 		}
10354 	};
10355 });
10356 
10357 // Included from: js/tinymce/classes/html/Writer.js
10358 
10359 /**
10360  * Writer.js
10361  *
10362  * Copyright, Moxiecode Systems AB
10363  * Released under LGPL License.
10364  *
10365  * License: http://www.tinymce.com/license
10366  * Contributing: http://www.tinymce.com/contributing
10367  */
10368 
10369 /**
10370  * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
10371  *
10372  * @class tinymce.html.Writer
10373  * @example
10374  * var writer = new tinymce.html.Writer({indent: true});
10375  * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
10376  * console.log(writer.getContent());
10377  *
10378  * @class tinymce.html.Writer
10379  * @version 3.4
10380  */
10381 define("tinymce/html/Writer", [
10382 	"tinymce/html/Entities",
10383 	"tinymce/util/Tools"
10384 ], function(Entities, Tools) {
10385 	var makeMap = Tools.makeMap;
10386 
10387 	/**
10388 	 * Constructs a new Writer instance.
10389 	 *
10390 	 * @constructor
10391 	 * @method Writer
10392 	 * @param {Object} settings Name/value settings object.
10393 	 */
10394 	return function(settings) {
10395 		var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
10396 
10397 		settings = settings || {};
10398 		indent = settings.indent;
10399 		indentBefore = makeMap(settings.indent_before || '');
10400 		indentAfter = makeMap(settings.indent_after || '');
10401 		encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
10402 		htmlOutput = settings.element_format == "html";
10403 
10404 		return {
10405 			/**
10406 			 * Writes the a start element such as <p id="a">.
10407 			 *
10408 			 * @method start
10409 			 * @param {String} name Name of the element.
10410 			 * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
10411 			 * @param {Boolean} empty Optional empty state if the tag should end like <br />.
10412 			 */
10413 			start: function(name, attrs, empty) {
10414 				var i, l, attr, value;
10415 
10416 				if (indent && indentBefore[name] && html.length > 0) {
10417 					value = html[html.length - 1];
10418 
10419 					if (value.length > 0 && value !== '\n') {
10420 						html.push('\n');
10421 					}
10422 				}
10423 
10424 				html.push('<', name);
10425 
10426 				if (attrs) {
10427 					for (i = 0, l = attrs.length; i < l; i++) {
10428 						attr = attrs[i];
10429 						html.push(' ', attr.name, '="', encode(attr.value, true), '"');
10430 					}
10431 				}
10432 
10433 				if (!empty || htmlOutput) {
10434 					html[html.length] = '>';
10435 				} else {
10436 					html[html.length] = ' />';
10437 				}
10438 
10439 				if (empty && indent && indentAfter[name] && html.length > 0) {
10440 					value = html[html.length - 1];
10441 
10442 					if (value.length > 0 && value !== '\n') {
10443 						html.push('\n');
10444 					}
10445 				}
10446 			},
10447 
10448 			/**
10449 			 * Writes the a end element such as </p>.
10450 			 *
10451 			 * @method end
10452 			 * @param {String} name Name of the element.
10453 			 */
10454 			end: function(name) {
10455 				var value;
10456 
10457 				/*if (indent && indentBefore[name] && html.length > 0) {
10458 					value = html[html.length - 1];
10459 
10460 					if (value.length > 0 && value !== '\n')
10461 						html.push('\n');
10462 				}*/
10463 
10464 				html.push('</', name, '>');
10465 
10466 				if (indent && indentAfter[name] && html.length > 0) {
10467 					value = html[html.length - 1];
10468 
10469 					if (value.length > 0 && value !== '\n') {
10470 						html.push('\n');
10471 					}
10472 				}
10473 			},
10474 
10475 			/**
10476 			 * Writes a text node.
10477 			 *
10478 			 * @method text
10479 			 * @param {String} text String to write out.
10480 			 * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
10481 			 */
10482 			text: function(text, raw) {
10483 				if (text.length > 0) {
10484 					html[html.length] = raw ? text : encode(text);
10485 				}
10486 			},
10487 
10488 			/**
10489 			 * Writes a cdata node such as <![CDATA[data]]>.
10490 			 *
10491 			 * @method cdata
10492 			 * @param {String} text String to write out inside the cdata.
10493 			 */
10494 			cdata: function(text) {
10495 				html.push('<![CDATA[', text, ']]>');
10496 			},
10497 
10498 			/**
10499 			 * Writes a comment node such as <!-- Comment -->.
10500 			 *
10501 			 * @method cdata
10502 			 * @param {String} text String to write out inside the comment.
10503 			 */
10504 			comment: function(text) {
10505 				html.push('<!--', text, '-->');
10506 			},
10507 
10508 			/**
10509 			 * Writes a PI node such as <?xml attr="value" ?>.
10510 			 *
10511 			 * @method pi
10512 			 * @param {String} name Name of the pi.
10513 			 * @param {String} text String to write out inside the pi.
10514 			 */
10515 			pi: function(name, text) {
10516 				if (text) {
10517 					html.push('<?', name, ' ', text, '?>');
10518 				} else {
10519 					html.push('<?', name, '?>');
10520 				}
10521 
10522 				if (indent) {
10523 					html.push('\n');
10524 				}
10525 			},
10526 
10527 			/**
10528 			 * Writes a doctype node such as <!DOCTYPE data>.
10529 			 *
10530 			 * @method doctype
10531 			 * @param {String} text String to write out inside the doctype.
10532 			 */
10533 			doctype: function(text) {
10534 				html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
10535 			},
10536 
10537 			/**
10538 			 * Resets the internal buffer if one wants to reuse the writer.
10539 			 *
10540 			 * @method reset
10541 			 */
10542 			reset: function() {
10543 				html.length = 0;
10544 			},
10545 
10546 			/**
10547 			 * Returns the contents that got serialized.
10548 			 *
10549 			 * @method getContent
10550 			 * @return {String} HTML contents that got written down.
10551 			 */
10552 			getContent: function() {
10553 				return html.join('').replace(/\n$/, '');
10554 			}
10555 		};
10556 	};
10557 });
10558 
10559 // Included from: js/tinymce/classes/html/Serializer.js
10560 
10561 /**
10562  * Serializer.js
10563  *
10564  * Copyright, Moxiecode Systems AB
10565  * Released under LGPL License.
10566  *
10567  * License: http://www.tinymce.com/license
10568  * Contributing: http://www.tinymce.com/contributing
10569  */
10570 
10571 /**
10572  * This class is used to serialize down the DOM tree into a string using a Writer instance.
10573  *
10574  *
10575  * @example
10576  * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
10577  * @class tinymce.html.Serializer
10578  * @version 3.4
10579  */
10580 define("tinymce/html/Serializer", [
10581 	"tinymce/html/Writer",
10582 	"tinymce/html/Schema"
10583 ], function(Writer, Schema) {
10584 	/**
10585 	 * Constructs a new Serializer instance.
10586 	 *
10587 	 * @constructor
10588 	 * @method Serializer
10589 	 * @param {Object} settings Name/value settings object.
10590 	 * @param {tinymce.html.Schema} schema Schema instance to use.
10591 	 */
10592 	return function(settings, schema) {
10593 		var self = this, writer = new Writer(settings);
10594 
10595 		settings = settings || {};
10596 		settings.validate = "validate" in settings ? settings.validate : true;
10597 
10598 		self.schema = schema = schema || new Schema();
10599 		self.writer = writer;
10600 
10601 		/**
10602 		 * Serializes the specified node into a string.
10603 		 *
10604 		 * @example
10605 		 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
10606 		 * @method serialize
10607 		 * @param {tinymce.html.Node} node Node instance to serialize.
10608 		 * @return {String} String with HTML based on DOM tree.
10609 		 */
10610 		self.serialize = function(node) {
10611 			var handlers, validate;
10612 
10613 			validate = settings.validate;
10614 
10615 			handlers = {
10616 				// #text
10617 				3: function(node) {
10618 					writer.text(node.value, node.raw);
10619 				},
10620 
10621 				// #comment
10622 				8: function(node) {
10623 					writer.comment(node.value);
10624 				},
10625 
10626 				// Processing instruction
10627 				7: function(node) {
10628 					writer.pi(node.name, node.value);
10629 				},
10630 
10631 				// Doctype
10632 				10: function(node) {
10633 					writer.doctype(node.value);
10634 				},
10635 
10636 				// CDATA
10637 				4: function(node) {
10638 					writer.cdata(node.value);
10639 				},
10640 
10641 				// Document fragment
10642 				11: function(node) {
10643 					if ((node = node.firstChild)) {
10644 						do {
10645 							walk(node);
10646 						} while ((node = node.next));
10647 					}
10648 				}
10649 			};
10650 
10651 			writer.reset();
10652 
10653 			function walk(node) {
10654 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
10655 
10656 				if (!handler) {
10657 					name = node.name;
10658 					isEmpty = node.shortEnded;
10659 					attrs = node.attributes;
10660 
10661 					// Sort attributes
10662 					if (validate && attrs && attrs.length > 1) {
10663 						sortedAttrs = [];
10664 						sortedAttrs.map = {};
10665 
10666 						elementRule = schema.getElementRule(node.name);
10667 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
10668 							attrName = elementRule.attributesOrder[i];
10669 
10670 							if (attrName in attrs.map) {
10671 								attrValue = attrs.map[attrName];
10672 								sortedAttrs.map[attrName] = attrValue;
10673 								sortedAttrs.push({name: attrName, value: attrValue});
10674 							}
10675 						}
10676 
10677 						for (i = 0, l = attrs.length; i < l; i++) {
10678 							attrName = attrs[i].name;
10679 
10680 							if (!(attrName in sortedAttrs.map)) {
10681 								attrValue = attrs.map[attrName];
10682 								sortedAttrs.map[attrName] = attrValue;
10683 								sortedAttrs.push({name: attrName, value: attrValue});
10684 							}
10685 						}
10686 
10687 						attrs = sortedAttrs;
10688 					}
10689 
10690 					writer.start(node.name, attrs, isEmpty);
10691 
10692 					if (!isEmpty) {
10693 						if ((node = node.firstChild)) {
10694 							do {
10695 								walk(node);
10696 							} while ((node = node.next));
10697 						}
10698 
10699 						writer.end(name);
10700 					}
10701 				} else {
10702 					handler(node);
10703 				}
10704 			}
10705 
10706 			// Serialize element and treat all non elements as fragments
10707 			if (node.type == 1 && !settings.inner) {
10708 				walk(node);
10709 			} else {
10710 				handlers[11](node);
10711 			}
10712 
10713 			return writer.getContent();
10714 		};
10715 	};
10716 });
10717 
10718 // Included from: js/tinymce/classes/dom/Serializer.js
10719 
10720 /**
10721  * Serializer.js
10722  *
10723  * Copyright, Moxiecode Systems AB
10724  * Released under LGPL License.
10725  *
10726  * License: http://www.tinymce.com/license
10727  * Contributing: http://www.tinymce.com/contributing
10728  */
10729 
10730 /**
10731  * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
10732  * more details and examples on how to use this class.
10733  *
10734  * @class tinymce.dom.Serializer
10735  */
10736 define("tinymce/dom/Serializer", [
10737 	"tinymce/dom/DOMUtils",
10738 	"tinymce/html/DomParser",
10739 	"tinymce/html/Entities",
10740 	"tinymce/html/Serializer",
10741 	"tinymce/html/Node",
10742 	"tinymce/html/Schema",
10743 	"tinymce/Env",
10744 	"tinymce/util/Tools"
10745 ], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
10746 	var each = Tools.each, trim = Tools.trim;
10747 	var DOM = DOMUtils.DOM;
10748 
10749 	/**
10750 	 * Constructs a new DOM serializer class.
10751 	 *
10752 	 * @constructor
10753 	 * @method Serializer
10754 	 * @param {Object} settings Serializer settings object.
10755 	 * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
10756 	 */
10757 	return function(settings, editor) {
10758 		var dom, schema, htmlParser;
10759 
10760 		if (editor) {
10761 			dom = editor.dom;
10762 			schema = editor.schema;
10763 		}
10764 
10765 		// Default DOM and Schema if they are undefined
10766 		dom = dom || DOM;
10767 		schema = schema || new Schema(settings);
10768 		settings.entity_encoding = settings.entity_encoding || 'named';
10769 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10770 
10771 		htmlParser = new DomParser(settings, schema);
10772 
10773 		// Convert tabindex back to elements when serializing contents
10774 		htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
10775 			var i = nodes.length, node;
10776 
10777 			while (i--) {
10778 				node = nodes[i];
10779 				node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
10780 				node.attr(name, null);
10781 			}
10782 		});
10783 
10784 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10785 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10786 			var i = nodes.length, node, value, internalName = 'data-mce-' + name;
10787 			var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10788 
10789 			while (i--) {
10790 				node = nodes[i];
10791 
10792 				value = node.attributes.map[internalName];
10793 				if (value !== undef) {
10794 					// Set external name to internal value and remove internal
10795 					node.attr(name, value.length > 0 ? value : null);
10796 					node.attr(internalName, null);
10797 				} else {
10798 					// No internal attribute found then convert the value we have in the DOM
10799 					value = node.attributes.map[name];
10800 
10801 					if (name === "style") {
10802 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10803 					} else if (urlConverter) {
10804 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10805 					}
10806 
10807 					node.attr(name, value.length > 0 ? value : null);
10808 				}
10809 			}
10810 		});
10811 
10812 		// Remove internal classes mceItem<..> or mceSelected
10813 		htmlParser.addAttributeFilter('class', function(nodes) {
10814 			var i = nodes.length, node, value;
10815 
10816 			while (i--) {
10817 				node = nodes[i];
10818 				value = node.attr('class');
10819 
10820 				if (value) {
10821 					value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
10822 					node.attr('class', value.length > 0 ? value : null);
10823 				}
10824 			}
10825 		});
10826 
10827 		// Remove bookmark elements
10828 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10829 			var i = nodes.length, node;
10830 
10831 			while (i--) {
10832 				node = nodes[i];
10833 
10834 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
10835 					node.remove();
10836 				}
10837 			}
10838 		});
10839 
10840 		htmlParser.addNodeFilter('noscript', function(nodes) {
10841 			var i = nodes.length, node;
10842 
10843 			while (i--) {
10844 				node = nodes[i].firstChild;
10845 
10846 				if (node) {
10847 					node.value = Entities.decode(node.value);
10848 				}
10849 			}
10850 		});
10851 
10852 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10853 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10854 			var i = nodes.length, node, value, type;
10855 
10856 			function trim(value) {
10857 				/*jshint maxlen:255 */
10858 				/*eslint max-len:0 */
10859 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10860 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10861 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10862 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10863 			}
10864 
10865 			while (i--) {
10866 				node = nodes[i];
10867 				value = node.firstChild ? node.firstChild.value : '';
10868 
10869 				if (name === "script") {
10870 					// Remove mce- prefix from script elements and remove default type since the user specified
10871 					// a script element without type attribute
10872 					type = node.attr('type');
10873 					if (type) {
10874 						node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
10875 					}
10876 
10877 					if (value.length > 0) {
10878 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10879 					}
10880 				} else {
10881 					if (value.length > 0) {
10882 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10883 					}
10884 				}
10885 			}
10886 		});
10887 
10888 		// Convert comments to cdata and handle protected comments
10889 		htmlParser.addNodeFilter('#comment', function(nodes) {
10890 			var i = nodes.length, node;
10891 
10892 			while (i--) {
10893 				node = nodes[i];
10894 
10895 				if (node.value.indexOf('[CDATA[') === 0) {
10896 					node.name = '#cdata';
10897 					node.type = 4;
10898 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10899 				} else if (node.value.indexOf('mce:protected ') === 0) {
10900 					node.name = "#text";
10901 					node.type = 3;
10902 					node.raw = true;
10903 					node.value = unescape(node.value).substr(14);
10904 				}
10905 			}
10906 		});
10907 
10908 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10909 			var i = nodes.length, node;
10910 
10911 			while (i--) {
10912 				node = nodes[i];
10913 				if (node.type === 7) {
10914 					node.remove();
10915 				} else if (node.type === 1) {
10916 					if (name === "input" && !("type" in node.attributes.map)) {
10917 						node.attr('type', 'text');
10918 					}
10919 				}
10920 			}
10921 		});
10922 
10923 		// Fix list elements, TODO: Replace this later
10924 		if (settings.fix_list_elements) {
10925 			htmlParser.addNodeFilter('ul,ol', function(nodes) {
10926 				var i = nodes.length, node, parentNode;
10927 
10928 				while (i--) {
10929 					node = nodes[i];
10930 					parentNode = node.parent;
10931 
10932 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10933 						if (node.prev && node.prev.name === 'li') {
10934 							node.prev.append(node);
10935 						}
10936 					}
10937 				}
10938 			});
10939 		}
10940 
10941 		// Remove internal data attributes
10942 		htmlParser.addAttributeFilter(
10943 			'data-mce-src,data-mce-href,data-mce-style,' +
10944 			'data-mce-selected,data-mce-expando,' +
10945 			'data-mce-type,data-mce-resize',
10946 
10947 			function(nodes, name) {
10948 				var i = nodes.length;
10949 
10950 				while (i--) {
10951 					nodes[i].attr(name, null);
10952 				}
10953 			}
10954 		);
10955 
10956 		// Return public methods
10957 		return {
10958 			/**
10959 			 * Schema instance that was used to when the Serializer was constructed.
10960 			 *
10961 			 * @field {tinymce.html.Schema} schema
10962 			 */
10963 			schema: schema,
10964 
10965 			/**
10966 			 * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
10967 			 * and then execute the callback ones it has finished parsing the document.
10968 			 *
10969 			 * @example
10970 			 * parser.addNodeFilter('p,h1', function(nodes, name) {
10971 			 *		for (var i = 0; i < nodes.length; i++) {
10972 			 *			console.log(nodes[i].name);
10973 			 *		}
10974 			 * });
10975 			 * @method addNodeFilter
10976 			 * @method {String} name Comma separated list of nodes to collect.
10977 			 * @param {function} callback Callback function to execute once it has collected nodes.
10978 			 */
10979 			addNodeFilter: htmlParser.addNodeFilter,
10980 
10981 			/**
10982 			 * Adds a attribute filter function to the parser used by the serializer, the parser will
10983 			 * collect nodes that has the specified attributes
10984 			 * and then execute the callback ones it has finished parsing the document.
10985 			 *
10986 			 * @example
10987 			 * parser.addAttributeFilter('src,href', function(nodes, name) {
10988 			 *		for (var i = 0; i < nodes.length; i++) {
10989 			 *			console.log(nodes[i].name);
10990 			 *		}
10991 			 * });
10992 			 * @method addAttributeFilter
10993 			 * @method {String} name Comma separated list of nodes to collect.
10994 			 * @param {function} callback Callback function to execute once it has collected nodes.
10995 			 */
10996 			addAttributeFilter: htmlParser.addAttributeFilter,
10997 
10998 			/**
10999 			 * Serializes the specified browser DOM node into a HTML string.
11000 			 *
11001 			 * @method serialize
11002 			 * @param {DOMNode} node DOM node to serialize.
11003 			 * @param {Object} args Arguments option that gets passed to event handlers.
11004 			 */
11005 			serialize: function(node, args) {
11006 				var self = this, impl, doc, oldDoc, htmlSerializer, content;
11007 
11008 				// Explorer won't clone contents of script and style and the
11009 				// selected index of select elements are cleared on a clone operation.
11010 				if (Env.ie && dom.select('script,style,select,map').length > 0) {
11011 					content = node.innerHTML;
11012 					node = node.cloneNode(false);
11013 					dom.setHTML(node, content);
11014 				} else {
11015 					node = node.cloneNode(true);
11016 				}
11017 
11018 				// Nodes needs to be attached to something in WebKit/Opera
11019 				// This fix will make DOM ranges and make Sizzle happy!
11020 				impl = node.ownerDocument.implementation;
11021 				if (impl.createHTMLDocument) {
11022 					// Create an empty HTML document
11023 					doc = impl.createHTMLDocument("");
11024 
11025 					// Add the element or it's children if it's a body element to the new document
11026 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
11027 						doc.body.appendChild(doc.importNode(node, true));
11028 					});
11029 
11030 					// Grab first child or body element for serialization
11031 					if (node.nodeName != 'BODY') {
11032 						node = doc.body.firstChild;
11033 					} else {
11034 						node = doc.body;
11035 					}
11036 
11037 					// set the new document in DOMUtils so createElement etc works
11038 					oldDoc = dom.doc;
11039 					dom.doc = doc;
11040 				}
11041 
11042 				args = args || {};
11043 				args.format = args.format || 'html';
11044 
11045 				// Don't wrap content if we want selected html
11046 				if (args.selection) {
11047 					args.forced_root_block = '';
11048 				}
11049 
11050 				// Pre process
11051 				if (!args.no_events) {
11052 					args.node = node;
11053 					self.onPreProcess(args);
11054 				}
11055 
11056 				// Setup serializer
11057 				htmlSerializer = new Serializer(settings, schema);
11058 
11059 				// Parse and serialize HTML
11060 				args.content = htmlSerializer.serialize(
11061 					htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
11062 				);
11063 
11064 				// Replace all BOM characters for now until we can find a better solution
11065 				if (!args.cleanup) {
11066 					args.content = args.content.replace(/\uFEFF/g, '');
11067 				}
11068 
11069 				// Post process
11070 				if (!args.no_events) {
11071 					self.onPostProcess(args);
11072 				}
11073 
11074 				// Restore the old document if it was changed
11075 				if (oldDoc) {
11076 					dom.doc = oldDoc;
11077 				}
11078 
11079 				args.node = null;
11080 
11081 				return args.content;
11082 			},
11083 
11084 			/**
11085 			 * Adds valid elements rules to the serializers schema instance this enables you to specify things
11086 			 * like what elements should be outputted and what attributes specific elements might have.
11087 			 * Consult the Wiki for more details on this format.
11088 			 *
11089 			 * @method addRules
11090 			 * @param {String} rules Valid elements rules string to add to schema.
11091 			 */
11092 			addRules: function(rules) {
11093 				schema.addValidElements(rules);
11094 			},
11095 
11096 			/**
11097 			 * Sets the valid elements rules to the serializers schema instance this enables you to specify things
11098 			 * like what elements should be outputted and what attributes specific elements might have.
11099 			 * Consult the Wiki for more details on this format.
11100 			 *
11101 			 * @method setRules
11102 			 * @param {String} rules Valid elements rules string.
11103 			 */
11104 			setRules: function(rules) {
11105 				schema.setValidElements(rules);
11106 			},
11107 
11108 			onPreProcess: function(args) {
11109 				if (editor) {
11110 					editor.fire('PreProcess', args);
11111 				}
11112 			},
11113 
11114 			onPostProcess: function(args) {
11115 				if (editor) {
11116 					editor.fire('PostProcess', args);
11117 				}
11118 			}
11119 		};
11120 	};
11121 });
11122 
11123 // Included from: js/tinymce/classes/dom/TridentSelection.js
11124 
11125 /**
11126  * TridentSelection.js
11127  *
11128  * Copyright, Moxiecode Systems AB
11129  * Released under LGPL License.
11130  *
11131  * License: http://www.tinymce.com/license
11132  * Contributing: http://www.tinymce.com/contributing
11133  */
11134 
11135 /**
11136  * Selection class for old explorer versions. This one fakes the
11137  * native selection object available on modern browsers.
11138  *
11139  * @class tinymce.dom.TridentSelection
11140  */
11141 define("tinymce/dom/TridentSelection", [], function() {
11142 	function Selection(selection) {
11143 		var self = this, dom = selection.dom, FALSE = false;
11144 
11145 		function getPosition(rng, start) {
11146 			var checkRng, startIndex = 0, endIndex, inside,
11147 				children, child, offset, index, position = -1, parent;
11148 
11149 			// Setup test range, collapse it and get the parent
11150 			checkRng = rng.duplicate();
11151 			checkRng.collapse(start);
11152 			parent = checkRng.parentElement();
11153 
11154 			// Check if the selection is within the right document
11155 			if (parent.ownerDocument !== selection.dom.doc) {
11156 				return;
11157 			}
11158 
11159 			// IE will report non editable elements as it's parent so look for an editable one
11160 			while (parent.contentEditable === "false") {
11161 				parent = parent.parentNode;
11162 			}
11163 
11164 			// If parent doesn't have any children then return that we are inside the element
11165 			if (!parent.hasChildNodes()) {
11166 				return {node: parent, inside: 1};
11167 			}
11168 
11169 			// Setup node list and endIndex
11170 			children = parent.children;
11171 			endIndex = children.length - 1;
11172 
11173 			// Perform a binary search for the position
11174 			while (startIndex <= endIndex) {
11175 				index = Math.floor((startIndex + endIndex) / 2);
11176 
11177 				// Move selection to node and compare the ranges
11178 				child = children[index];
11179 				checkRng.moveToElementText(child);
11180 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
11181 
11182 				// Before/after or an exact match
11183 				if (position > 0) {
11184 					endIndex = index - 1;
11185 				} else if (position < 0) {
11186 					startIndex = index + 1;
11187 				} else {
11188 					return {node: child};
11189 				}
11190 			}
11191 
11192 			// Check if child position is before or we didn't find a position
11193 			if (position < 0) {
11194 				// No element child was found use the parent element and the offset inside that
11195 				if (!child) {
11196 					checkRng.moveToElementText(parent);
11197 					checkRng.collapse(true);
11198 					child = parent;
11199 					inside = true;
11200 				} else {
11201 					checkRng.collapse(false);
11202 				}
11203 
11204 				// Walk character by character in text node until we hit the selected range endpoint,
11205 				// hit the end of document or parent isn't the right one
11206 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
11207 				offset = 0;
11208 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
11209 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
11210 						break;
11211 					}
11212 
11213 					offset++;
11214 				}
11215 			} else {
11216 				// Child position is after the selection endpoint
11217 				checkRng.collapse(true);
11218 
11219 				// Walk character by character in text node until we hit the selected range endpoint, hit
11220 				// the end of document or parent isn't the right one
11221 				offset = 0;
11222 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
11223 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
11224 						break;
11225 					}
11226 
11227 					offset++;
11228 				}
11229 			}
11230 
11231 			return {node: child, position: position, offset: offset, inside: inside};
11232 		}
11233 
11234 		// Returns a W3C DOM compatible range object by using the IE Range API
11235 		function getRange() {
11236 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
11237 
11238 			// If selection is outside the current document just return an empty range
11239 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
11240 			if (element.ownerDocument != dom.doc) {
11241 				return domRange;
11242 			}
11243 
11244 			collapsed = selection.isCollapsed();
11245 
11246 			// Handle control selection
11247 			if (ieRange.item) {
11248 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
11249 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
11250 
11251 				return domRange;
11252 			}
11253 
11254 			function findEndPoint(start) {
11255 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
11256 
11257 				container = endPoint.node;
11258 				offset = endPoint.offset;
11259 
11260 				if (endPoint.inside && !container.hasChildNodes()) {
11261 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
11262 					return;
11263 				}
11264 
11265 				if (offset === undef) {
11266 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
11267 					return;
11268 				}
11269 
11270 				if (endPoint.position < 0) {
11271 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
11272 
11273 					if (!sibling) {
11274 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
11275 						return;
11276 					}
11277 
11278 					if (!offset) {
11279 						if (sibling.nodeType == 3) {
11280 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
11281 						} else {
11282 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
11283 						}
11284 
11285 						return;
11286 					}
11287 
11288 					// Find the text node and offset
11289 					while (sibling) {
11290 						if (sibling.nodeType == 3) {
11291 							nodeValue = sibling.nodeValue;
11292 							textNodeOffset += nodeValue.length;
11293 
11294 							// We are at or passed the position we where looking for
11295 							if (textNodeOffset >= offset) {
11296 								container = sibling;
11297 								textNodeOffset -= offset;
11298 								textNodeOffset = nodeValue.length - textNodeOffset;
11299 								break;
11300 							}
11301 						}
11302 
11303 						sibling = sibling.nextSibling;
11304 					}
11305 				} else {
11306 					// Find the text node and offset
11307 					sibling = container.previousSibling;
11308 
11309 					if (!sibling) {
11310 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
11311 					}
11312 
11313 					// If there isn't any text to loop then use the first position
11314 					if (!offset) {
11315 						if (container.nodeType == 3) {
11316 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
11317 						} else {
11318 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
11319 						}
11320 
11321 						return;
11322 					}
11323 
11324 					while (sibling) {
11325 						if (sibling.nodeType == 3) {
11326 							textNodeOffset += sibling.nodeValue.length;
11327 
11328 							// We are at or passed the position we where looking for
11329 							if (textNodeOffset >= offset) {
11330 								container = sibling;
11331 								textNodeOffset -= offset;
11332 								break;
11333 							}
11334 						}
11335 
11336 						sibling = sibling.previousSibling;
11337 					}
11338 				}
11339 
11340 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
11341 			}
11342 
11343 			try {
11344 				// Find start point
11345 				findEndPoint(true);
11346 
11347 				// Find end point if needed
11348 				if (!collapsed) {
11349 					findEndPoint();
11350 				}
11351 			} catch (ex) {
11352 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
11353 				// access the nodeValue or other properties of text nodes. This seems to happend when
11354 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
11355 				if (ex.number == -2147024809) {
11356 					// Get the current selection
11357 					bookmark = self.getBookmark(2);
11358 
11359 					// Get start element
11360 					tmpRange = ieRange.duplicate();
11361 					tmpRange.collapse(true);
11362 					element = tmpRange.parentElement();
11363 
11364 					// Get end element
11365 					if (!collapsed) {
11366 						tmpRange = ieRange.duplicate();
11367 						tmpRange.collapse(false);
11368 						element2 = tmpRange.parentElement();
11369 						element2.innerHTML = element2.innerHTML;
11370 					}
11371 
11372 					// Remove the broken elements
11373 					element.innerHTML = element.innerHTML;
11374 
11375 					// Restore the selection
11376 					self.moveToBookmark(bookmark);
11377 
11378 					// Since the range has moved we need to re-get it
11379 					ieRange = selection.getRng();
11380 
11381 					// Find start point
11382 					findEndPoint(true);
11383 
11384 					// Find end point if needed
11385 					if (!collapsed) {
11386 						findEndPoint();
11387 					}
11388 				} else {
11389 					throw ex; // Throw other errors
11390 				}
11391 			}
11392 
11393 			return domRange;
11394 		}
11395 
11396 		this.getBookmark = function(type) {
11397 			var rng = selection.getRng(), bookmark = {};
11398 
11399 			function getIndexes(node) {
11400 				var parent, root, children, i, indexes = [];
11401 
11402 				parent = node.parentNode;
11403 				root = dom.getRoot().parentNode;
11404 
11405 				while (parent != root && parent.nodeType !== 9) {
11406 					children = parent.children;
11407 
11408 					i = children.length;
11409 					while (i--) {
11410 						if (node === children[i]) {
11411 							indexes.push(i);
11412 							break;
11413 						}
11414 					}
11415 
11416 					node = parent;
11417 					parent = parent.parentNode;
11418 				}
11419 
11420 				return indexes;
11421 			}
11422 
11423 			function getBookmarkEndPoint(start) {
11424 				var position;
11425 
11426 				position = getPosition(rng, start);
11427 				if (position) {
11428 					return {
11429 						position: position.position,
11430 						offset: position.offset,
11431 						indexes: getIndexes(position.node),
11432 						inside: position.inside
11433 					};
11434 				}
11435 			}
11436 
11437 			// Non ubstructive bookmark
11438 			if (type === 2) {
11439 				// Handle text selection
11440 				if (!rng.item) {
11441 					bookmark.start = getBookmarkEndPoint(true);
11442 
11443 					if (!selection.isCollapsed()) {
11444 						bookmark.end = getBookmarkEndPoint();
11445 					}
11446 				} else {
11447 					bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
11448 				}
11449 			}
11450 
11451 			return bookmark;
11452 		};
11453 
11454 		this.moveToBookmark = function(bookmark) {
11455 			var rng, body = dom.doc.body;
11456 
11457 			function resolveIndexes(indexes) {
11458 				var node, i, idx, children;
11459 
11460 				node = dom.getRoot();
11461 				for (i = indexes.length - 1; i >= 0; i--) {
11462 					children = node.children;
11463 					idx = indexes[i];
11464 
11465 					if (idx <= children.length - 1) {
11466 						node = children[idx];
11467 					}
11468 				}
11469 
11470 				return node;
11471 			}
11472 
11473 			function setBookmarkEndPoint(start) {
11474 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
11475 
11476 				if (endPoint) {
11477 					moveLeft = endPoint.position > 0;
11478 
11479 					moveRng = body.createTextRange();
11480 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
11481 
11482 					offset = endPoint.offset;
11483 					if (offset !== undef) {
11484 						moveRng.collapse(endPoint.inside || moveLeft);
11485 						moveRng.moveStart('character', moveLeft ? -offset : offset);
11486 					} else {
11487 						moveRng.collapse(start);
11488 					}
11489 
11490 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
11491 
11492 					if (start) {
11493 						rng.collapse(true);
11494 					}
11495 				}
11496 			}
11497 
11498 			if (bookmark.start) {
11499 				if (bookmark.start.ctrl) {
11500 					rng = body.createControlRange();
11501 					rng.addElement(resolveIndexes(bookmark.start.indexes));
11502 					rng.select();
11503 				} else {
11504 					rng = body.createTextRange();
11505 					setBookmarkEndPoint(true);
11506 					setBookmarkEndPoint();
11507 					rng.select();
11508 				}
11509 			}
11510 		};
11511 
11512 		this.addRange = function(rng) {
11513 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
11514 				doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
11515 
11516 			function setEndPoint(start) {
11517 				var container, offset, marker, tmpRng, nodes;
11518 
11519 				marker = dom.create('a');
11520 				container = start ? startContainer : endContainer;
11521 				offset = start ? startOffset : endOffset;
11522 				tmpRng = ieRng.duplicate();
11523 
11524 				if (container == doc || container == doc.documentElement) {
11525 					container = body;
11526 					offset = 0;
11527 				}
11528 
11529 				if (container.nodeType == 3) {
11530 					container.parentNode.insertBefore(marker, container);
11531 					tmpRng.moveToElementText(marker);
11532 					tmpRng.moveStart('character', offset);
11533 					dom.remove(marker);
11534 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
11535 				} else {
11536 					nodes = container.childNodes;
11537 
11538 					if (nodes.length) {
11539 						if (offset >= nodes.length) {
11540 							dom.insertAfter(marker, nodes[nodes.length - 1]);
11541 						} else {
11542 							container.insertBefore(marker, nodes[offset]);
11543 						}
11544 
11545 						tmpRng.moveToElementText(marker);
11546 					} else if (container.canHaveHTML) {
11547 						// Empty node selection for example <div>|</div>
11548 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
11549 						container.innerHTML = '<span></span>';
11550 						marker = container.firstChild;
11551 						tmpRng.moveToElementText(marker);
11552 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
11553 					}
11554 
11555 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
11556 					dom.remove(marker);
11557 				}
11558 			}
11559 
11560 			// Setup some shorter versions
11561 			startContainer = rng.startContainer;
11562 			startOffset = rng.startOffset;
11563 			endContainer = rng.endContainer;
11564 			endOffset = rng.endOffset;
11565 			ieRng = body.createTextRange();
11566 
11567 			// If single element selection then try making a control selection out of it
11568 			if (startContainer == endContainer && startContainer.nodeType == 1) {
11569 				// Trick to place the caret inside an empty block element like <p></p>
11570 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
11571 					if (startContainer.canHaveHTML) {
11572 						// Check if previous sibling is an empty block if it is then we need to render it
11573 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
11574 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
11575 						sibling = startContainer.previousSibling;
11576 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
11577 							sibling.innerHTML = '';
11578 						} else {
11579 							sibling = null;
11580 						}
11581 
11582 						startContainer.innerHTML = '<span></span><span></span>';
11583 						ieRng.moveToElementText(startContainer.lastChild);
11584 						ieRng.select();
11585 						dom.doc.selection.clear();
11586 						startContainer.innerHTML = '';
11587 
11588 						if (sibling) {
11589 							sibling.innerHTML = '';
11590 						}
11591 						return;
11592 					} else {
11593 						startOffset = dom.nodeIndex(startContainer);
11594 						startContainer = startContainer.parentNode;
11595 					}
11596 				}
11597 
11598 				if (startOffset == endOffset - 1) {
11599 					try {
11600 						ctrlElm = startContainer.childNodes[startOffset];
11601 						ctrlRng = body.createControlRange();
11602 						ctrlRng.addElement(ctrlElm);
11603 						ctrlRng.select();
11604 
11605 						// Check if the range produced is on the correct element and is a control range
11606 						// On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
11607 						nativeRng = selection.getRng();
11608 						if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
11609 							return;
11610 						}
11611 					} catch (ex) {
11612 						// Ignore
11613 					}
11614 				}
11615 			}
11616 
11617 			// Set start/end point of selection
11618 			setEndPoint(true);
11619 			setEndPoint();
11620 
11621 			// Select the new range and scroll it into view
11622 			ieRng.select();
11623 		};
11624 
11625 		// Expose range method
11626 		this.getRangeAt = getRange;
11627 	}
11628 
11629 	return Selection;
11630 });
11631 
11632 // Included from: js/tinymce/classes/util/VK.js
11633 
11634 /**
11635  * VK.js
11636  *
11637  * Copyright, Moxiecode Systems AB
11638  * Released under LGPL License.
11639  *
11640  * License: http://www.tinymce.com/license
11641  * Contributing: http://www.tinymce.com/contributing
11642  */
11643 
11644 /**
11645  * This file exposes a set of the common KeyCodes for use.  Please grow it as needed.
11646  */
11647 define("tinymce/util/VK", [
11648 	"tinymce/Env"
11649 ], function(Env) {
11650 	return {
11651 		BACKSPACE: 8,
11652 		DELETE: 46,
11653 		DOWN: 40,
11654 		ENTER: 13,
11655 		LEFT: 37,
11656 		RIGHT: 39,
11657 		SPACEBAR: 32,
11658 		TAB: 9,
11659 		UP: 38,
11660 
11661 		modifierPressed: function(e) {
11662 			return e.shiftKey || e.ctrlKey || e.altKey;
11663 		},
11664 
11665 		metaKeyPressed: function(e) {
11666 			// Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
11667 			return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
11668 		}
11669 	};
11670 });
11671 
11672 // Included from: js/tinymce/classes/dom/ControlSelection.js
11673 
11674 /**
11675  * ControlSelection.js
11676  *
11677  * Copyright, Moxiecode Systems AB
11678  * Released under LGPL License.
11679  *
11680  * License: http://www.tinymce.com/license
11681  * Contributing: http://www.tinymce.com/contributing
11682  */
11683 
11684 /**
11685  * This class handles control selection of elements. Controls are elements
11686  * that can be resized and needs to be selected as a whole. It adds custom resize handles
11687  * to all browser engines that support properly disabling the built in resize logic.
11688  *
11689  * @class tinymce.dom.ControlSelection
11690  */
11691 define("tinymce/dom/ControlSelection", [
11692 	"tinymce/util/VK",
11693 	"tinymce/util/Tools",
11694 	"tinymce/Env"
11695 ], function(VK, Tools, Env) {
11696 	return function(selection, editor) {
11697 		var dom = editor.dom, each = Tools.each;
11698 		var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
11699 		var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
11700 		var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
11701 		var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
11702 
11703 		// Details about each resize handle how to scale etc
11704 		resizeHandles = {
11705 			// Name: x multiplier, y multiplier, delta size x, delta size y
11706 			n:  [0.5,   0,     0,   -1],
11707 			e:  [1,    0.5,    1,    0],
11708 			s:  [0.5,   1,     0,    1],
11709 			w:  [0,    0.5,   -1,    0],
11710 			nw: [0,     0,    -1,   -1],
11711 			ne: [1,     0,     1,   -1],
11712 			se: [1,     1,     1,    1],
11713 			sw: [0,     1,    -1,    1]
11714 		};
11715 
11716 		// Add CSS for resize handles, cloned element and selected
11717 		var rootClass = '.mce-content-body';
11718 		editor.contentStyles.push(
11719 			rootClass + ' div.mce-resizehandle {' +
11720 				'position: absolute;' +
11721 				'border: 1px solid black;' +
11722 				'background: #FFF;' +
11723 				'width: 5px;' +
11724 				'height: 5px;' +
11725 				'z-index: 10000' +
11726 			'}' +
11727 			rootClass + ' .mce-resizehandle:hover {' +
11728 				'background: #000' +
11729 			'}' +
11730 			rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
11731 				'outline: 1px solid black;' +
11732 				'resize: none' + // Have been talks about implementing this in browsers
11733 			'}' +
11734 			rootClass + ' .mce-clonedresizable {' +
11735 				'position: absolute;' +
11736 				(Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
11737 				'opacity: .5;' +
11738 				'filter: alpha(opacity=50);' +
11739 				'z-index: 10000' +
11740 			'}' +
11741 			rootClass + ' .mce-resize-helper {' +
11742 				'background: #555;' +
11743 				'background: rgba(0,0,0,0.75);' +
11744 				'border-radius: 3px;' +
11745 				'border: 1px;' +
11746 				'color: white;' +
11747 				'display: none;' +
11748 				'font-family: sans-serif;' +
11749 				'font-size: 12px;' +
11750 				'white-space: nowrap;' +
11751 				'line-height: 14px;' +
11752 				'margin: 5px 10px;' +
11753 				'padding: 5px;' +
11754 				'position: absolute;' +
11755 				'z-index: 10001' +
11756 			'}'
11757 		);
11758 
11759 		function isResizable(elm) {
11760 			var selector = editor.settings.object_resizing;
11761 
11762 			if (selector === false || Env.iOS) {
11763 				return false;
11764 			}
11765 
11766 			if (typeof selector != 'string') {
11767 				selector = 'table,img,div';
11768 			}
11769 
11770 			if (elm.getAttribute('data-mce-resize') === 'false') {
11771 				return false;
11772 			}
11773 
11774 			return editor.dom.is(elm, selector);
11775 		}
11776 
11777 		function resizeGhostElement(e) {
11778 			var deltaX, deltaY, proportional;
11779 			var resizeHelperX, resizeHelperY;
11780 
11781 			// Calc new width/height
11782 			deltaX = e.screenX - startX;
11783 			deltaY = e.screenY - startY;
11784 
11785 			// Calc new size
11786 			width = deltaX * selectedHandle[2] + startW;
11787 			height = deltaY * selectedHandle[3] + startH;
11788 
11789 			// Never scale down lower than 5 pixels
11790 			width = width < 5 ? 5 : width;
11791 			height = height < 5 ? 5 : height;
11792 
11793 			if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
11794 				proportional = !VK.modifierPressed(e);
11795 			} else {
11796 				proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
11797 			}
11798 
11799 			// Constrain proportions
11800 			if (proportional) {
11801 				if (abs(deltaX) > abs(deltaY)) {
11802 					height = round(width * ratio);
11803 					width = round(height / ratio);
11804 				} else {
11805 					width = round(height / ratio);
11806 					height = round(width * ratio);
11807 				}
11808 			}
11809 
11810 			// Update ghost size
11811 			dom.setStyles(selectedElmGhost, {
11812 				width: width,
11813 				height: height
11814 			});
11815 
11816 			// Update resize helper position
11817 			resizeHelperX = selectedHandle.startPos.x + deltaX;
11818 			resizeHelperY = selectedHandle.startPos.y + deltaY;
11819 			resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
11820 			resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
11821 
11822 			dom.setStyles(resizeHelper, {
11823 				left: resizeHelperX,
11824 				top: resizeHelperY,
11825 				display: 'block'
11826 			});
11827 
11828 			resizeHelper.innerHTML = width + ' × ' + height;
11829 
11830 			// Update ghost X position if needed
11831 			if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
11832 				dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
11833 			}
11834 
11835 			// Update ghost Y position if needed
11836 			if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
11837 				dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
11838 			}
11839 
11840 			// Calculate how must overflow we got
11841 			deltaX = rootElement.scrollWidth - startScrollWidth;
11842 			deltaY = rootElement.scrollHeight - startScrollHeight;
11843 
11844 			// Re-position the resize helper based on the overflow
11845 			if (deltaX + deltaY !== 0) {
11846 				dom.setStyles(resizeHelper, {
11847 					left: resizeHelperX - deltaX,
11848 					top: resizeHelperY - deltaY
11849 				});
11850 			}
11851 
11852 			if (!resizeStarted) {
11853 				editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
11854 				resizeStarted = true;
11855 			}
11856 		}
11857 
11858 		function endGhostResize() {
11859 			resizeStarted = false;
11860 
11861 			function setSizeProp(name, value) {
11862 				if (value) {
11863 					// Resize by using style or attribute
11864 					if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
11865 						dom.setStyle(selectedElm, name, value);
11866 					} else {
11867 						dom.setAttrib(selectedElm, name, value);
11868 					}
11869 				}
11870 			}
11871 
11872 			// Set width/height properties
11873 			setSizeProp('width', width);
11874 			setSizeProp('height', height);
11875 
11876 			dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
11877 			dom.unbind(editableDoc, 'mouseup', endGhostResize);
11878 
11879 			if (rootDocument != editableDoc) {
11880 				dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
11881 				dom.unbind(rootDocument, 'mouseup', endGhostResize);
11882 			}
11883 
11884 			// Remove ghost/helper and update resize handle positions
11885 			dom.remove(selectedElmGhost);
11886 			dom.remove(resizeHelper);
11887 
11888 			if (!isIE || selectedElm.nodeName == "TABLE") {
11889 				showResizeRect(selectedElm);
11890 			}
11891 
11892 			editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
11893 			dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
11894 			editor.nodeChanged();
11895 		}
11896 
11897 		function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
11898 			var position, targetWidth, targetHeight, e, rect;
11899 
11900 			unbindResizeHandleEvents();
11901 
11902 			// Get position and size of target
11903 			position = dom.getPos(targetElm, rootElement);
11904 			selectedElmX = position.x;
11905 			selectedElmY = position.y;
11906 			rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
11907 			targetWidth = rect.width || (rect.right - rect.left);
11908 			targetHeight = rect.height || (rect.bottom - rect.top);
11909 
11910 			// Reset width/height if user selects a new image/table
11911 			if (selectedElm != targetElm) {
11912 				detachResizeStartListener();
11913 				selectedElm = targetElm;
11914 				width = height = 0;
11915 			}
11916 
11917 			// Makes it possible to disable resizing
11918 			e = editor.fire('ObjectSelected', {target: targetElm});
11919 
11920 			if (isResizable(targetElm) && !e.isDefaultPrevented()) {
11921 				each(resizeHandles, function(handle, name) {
11922 					var handleElm, handlerContainerElm;
11923 
11924 					function startDrag(e) {
11925 						startX = e.screenX;
11926 						startY = e.screenY;
11927 						startW = selectedElm.clientWidth;
11928 						startH = selectedElm.clientHeight;
11929 						ratio = startH / startW;
11930 						selectedHandle = handle;
11931 
11932 						handle.startPos = {
11933 							x: targetWidth * handle[0] + selectedElmX,
11934 							y: targetHeight * handle[1] + selectedElmY
11935 						};
11936 
11937 						startScrollWidth = rootElement.scrollWidth;
11938 						startScrollHeight = rootElement.scrollHeight;
11939 
11940 						selectedElmGhost = selectedElm.cloneNode(true);
11941 						dom.addClass(selectedElmGhost, 'mce-clonedresizable');
11942 						dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
11943 						selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
11944 						selectedElmGhost.unSelectabe = true;
11945 						dom.setStyles(selectedElmGhost, {
11946 							left: selectedElmX,
11947 							top: selectedElmY,
11948 							margin: 0
11949 						});
11950 
11951 						selectedElmGhost.removeAttribute('data-mce-selected');
11952 						rootElement.appendChild(selectedElmGhost);
11953 
11954 						dom.bind(editableDoc, 'mousemove', resizeGhostElement);
11955 						dom.bind(editableDoc, 'mouseup', endGhostResize);
11956 
11957 						if (rootDocument != editableDoc) {
11958 							dom.bind(rootDocument, 'mousemove', resizeGhostElement);
11959 							dom.bind(rootDocument, 'mouseup', endGhostResize);
11960 						}
11961 
11962 						resizeHelper = dom.add(rootElement, 'div', {
11963 							'class': 'mce-resize-helper',
11964 							'data-mce-bogus': 'all'
11965 						}, startW + ' × ' + startH);
11966 					}
11967 
11968 					if (mouseDownHandleName) {
11969 						// Drag started by IE native resizestart
11970 						if (name == mouseDownHandleName) {
11971 							startDrag(mouseDownEvent);
11972 						}
11973 
11974 						return;
11975 					}
11976 
11977 					// Get existing or render resize handle
11978 					handleElm = dom.get('mceResizeHandle' + name);
11979 					if (!handleElm) {
11980 						handlerContainerElm = rootElement;
11981 
11982 						handleElm = dom.add(handlerContainerElm, 'div', {
11983 							id: 'mceResizeHandle' + name,
11984 							'data-mce-bogus': 'all',
11985 							'class': 'mce-resizehandle',
11986 							unselectable: true,
11987 							style: 'cursor:' + name + '-resize; margin:0; padding:0'
11988 						});
11989 
11990 						// Hides IE move layer cursor
11991 						// If we set it on Chrome we get this wounderful bug: #6725
11992 						if (Env.ie) {
11993 							handleElm.contentEditable = false;
11994 						}
11995 					} else {
11996 						dom.show(handleElm);
11997 					}
11998 
11999 					if (!handle.elm) {
12000 						dom.bind(handleElm, 'mousedown', function(e) {
12001 							e.stopImmediatePropagation();
12002 							e.preventDefault();
12003 							startDrag(e);
12004 						});
12005 
12006 						handle.elm = handleElm;
12007 					}
12008 
12009 					// Position element
12010 					dom.setStyles(handleElm, {
12011 						left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
12012 						top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
12013 					});
12014 				});
12015 			} else {
12016 				hideResizeRect();
12017 			}
12018 
12019 			selectedElm.setAttribute('data-mce-selected', '1');
12020 		}
12021 
12022 		function hideResizeRect() {
12023 			var name, handleElm;
12024 
12025 			unbindResizeHandleEvents();
12026 
12027 			if (selectedElm) {
12028 				selectedElm.removeAttribute('data-mce-selected');
12029 			}
12030 
12031 			for (name in resizeHandles) {
12032 				handleElm = dom.get('mceResizeHandle' + name);
12033 				if (handleElm) {
12034 					dom.unbind(handleElm);
12035 					dom.remove(handleElm);
12036 				}
12037 			}
12038 		}
12039 
12040 		function updateResizeRect(e) {
12041 			var startElm, controlElm;
12042 
12043 			function isChildOrEqual(node, parent) {
12044 				if (node) {
12045 					do {
12046 						if (node === parent) {
12047 							return true;
12048 						}
12049 					} while ((node = node.parentNode));
12050 				}
12051 			}
12052 
12053 			// Ignore all events while resizing
12054 			if (resizeStarted) {
12055 				return;
12056 			}
12057 
12058 			// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
12059 			each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
12060 				img.removeAttribute('data-mce-selected');
12061 			});
12062 
12063 			controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
12064 			controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
12065 
12066 			if (isChildOrEqual(controlElm, rootElement)) {
12067 				disableGeckoResize();
12068 				startElm = selection.getStart(true);
12069 
12070 				if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
12071 					if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
12072 						showResizeRect(controlElm);
12073 						return;
12074 					}
12075 				}
12076 			}
12077 
12078 			hideResizeRect();
12079 		}
12080 
12081 		function attachEvent(elm, name, func) {
12082 			if (elm && elm.attachEvent) {
12083 				elm.attachEvent('on' + name, func);
12084 			}
12085 		}
12086 
12087 		function detachEvent(elm, name, func) {
12088 			if (elm && elm.detachEvent) {
12089 				elm.detachEvent('on' + name, func);
12090 			}
12091 		}
12092 
12093 		function resizeNativeStart(e) {
12094 			var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
12095 
12096 			pos = target.getBoundingClientRect();
12097 			relativeX = lastMouseDownEvent.clientX - pos.left;
12098 			relativeY = lastMouseDownEvent.clientY - pos.top;
12099 
12100 			// Figure out what corner we are draging on
12101 			for (name in resizeHandles) {
12102 				corner = resizeHandles[name];
12103 
12104 				cornerX = target.offsetWidth * corner[0];
12105 				cornerY = target.offsetHeight * corner[1];
12106 
12107 				if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
12108 					selectedHandle = corner;
12109 					break;
12110 				}
12111 			}
12112 
12113 			// Remove native selection and let the magic begin
12114 			resizeStarted = true;
12115 			editor.fire('ObjectResizeStart', {
12116 				target: selectedElm,
12117 				width: selectedElm.clientWidth,
12118 				height: selectedElm.clientHeight
12119 			});
12120 			editor.getDoc().selection.empty();
12121 			showResizeRect(target, name, lastMouseDownEvent);
12122 		}
12123 
12124 		function nativeControlSelect(e) {
12125 			var target = e.srcElement;
12126 
12127 			if (target != selectedElm) {
12128 				editor.fire('ObjectSelected', {target: target});
12129 				detachResizeStartListener();
12130 
12131 				if (target.id.indexOf('mceResizeHandle') === 0) {
12132 					e.returnValue = false;
12133 					return;
12134 				}
12135 
12136 				if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
12137 					hideResizeRect();
12138 					selectedElm = target;
12139 					attachEvent(target, 'resizestart', resizeNativeStart);
12140 				}
12141 			}
12142 		}
12143 
12144 		function detachResizeStartListener() {
12145 			detachEvent(selectedElm, 'resizestart', resizeNativeStart);
12146 		}
12147 
12148 		function unbindResizeHandleEvents() {
12149 			for (var name in resizeHandles) {
12150 				var handle = resizeHandles[name];
12151 
12152 				if (handle.elm) {
12153 					dom.unbind(handle.elm);
12154 					delete handle.elm;
12155 				}
12156 			}
12157 		}
12158 
12159 		function disableGeckoResize() {
12160 			try {
12161 				// Disable object resizing on Gecko
12162 				editor.getDoc().execCommand('enableObjectResizing', false, false);
12163 			} catch (ex) {
12164 				// Ignore
12165 			}
12166 		}
12167 
12168 		function controlSelect(elm) {
12169 			var ctrlRng;
12170 
12171 			if (!isIE) {
12172 				return;
12173 			}
12174 
12175 			ctrlRng = editableDoc.body.createControlRange();
12176 
12177 			try {
12178 				ctrlRng.addElement(elm);
12179 				ctrlRng.select();
12180 				return true;
12181 			} catch (ex) {
12182 				// Ignore since the element can't be control selected for example a P tag
12183 			}
12184 		}
12185 
12186 		editor.on('init', function() {
12187 			if (isIE) {
12188 				// Hide the resize rect on resize and reselect the image
12189 				editor.on('ObjectResized', function(e) {
12190 					if (e.target.nodeName != 'TABLE') {
12191 						hideResizeRect();
12192 						controlSelect(e.target);
12193 					}
12194 				});
12195 
12196 				attachEvent(rootElement, 'controlselect', nativeControlSelect);
12197 
12198 				editor.on('mousedown', function(e) {
12199 					lastMouseDownEvent = e;
12200 				});
12201 			} else {
12202 				disableGeckoResize();
12203 
12204 				if (Env.ie >= 11) {
12205 					// TODO: Drag/drop doesn't work
12206 					editor.on('mouseup', function(e) {
12207 						var nodeName = e.target.nodeName;
12208 
12209 						if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
12210 							editor.selection.select(e.target, nodeName == 'TABLE');
12211 							editor.nodeChanged();
12212 						}
12213 					});
12214 
12215 					editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
12216 						if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
12217 							e.preventDefault();
12218 
12219 							// This moves the selection from being a control selection to a text like selection like in WebKit #6753
12220 							// TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
12221 							if (e.target.tagName == 'IMG') {
12222 								window.setTimeout(function() {
12223 									editor.selection.select(e.target);
12224 								}, 0);
12225 							}
12226 						}
12227 					});
12228 				}
12229 			}
12230 
12231 			editor.on('nodechange ResizeEditor', updateResizeRect);
12232 
12233 			// Update resize rect while typing in a table
12234 			editor.on('keydown keyup', function(e) {
12235 				if (selectedElm && selectedElm.nodeName == "TABLE") {
12236 					updateResizeRect(e);
12237 				}
12238 			});
12239 
12240 			editor.on('hide', hideResizeRect);
12241 
12242 			// Hide rect on focusout since it would float on top of windows otherwise
12243 			//editor.on('focusout', hideResizeRect);
12244 		});
12245 
12246 		editor.on('remove', unbindResizeHandleEvents);
12247 
12248 		function destroy() {
12249 			selectedElm = selectedElmGhost = null;
12250 
12251 			if (isIE) {
12252 				detachResizeStartListener();
12253 				detachEvent(rootElement, 'controlselect', nativeControlSelect);
12254 			}
12255 		}
12256 
12257 		return {
12258 			isResizable: isResizable,
12259 			showResizeRect: showResizeRect,
12260 			hideResizeRect: hideResizeRect,
12261 			updateResizeRect: updateResizeRect,
12262 			controlSelect: controlSelect,
12263 			destroy: destroy
12264 		};
12265 	};
12266 });
12267 
12268 // Included from: js/tinymce/classes/dom/BookmarkManager.js
12269 
12270 /**
12271  * BookmarkManager.js
12272  *
12273  * Copyright, Moxiecode Systems AB
12274  * Released under LGPL License.
12275  *
12276  * License: http://www.tinymce.com/license
12277  * Contributing: http://www.tinymce.com/contributing
12278  */
12279 
12280 /**
12281  * This class handles selection bookmarks.
12282  *
12283  * @class tinymce.dom.BookmarkManager
12284  */
12285 define("tinymce/dom/BookmarkManager", [
12286 	"tinymce/Env",
12287 	"tinymce/util/Tools"
12288 ], function(Env, Tools) {
12289 	/**
12290 	 * Constructs a new BookmarkManager instance for a specific selection instance.
12291 	 *
12292 	 * @constructor
12293 	 * @method BookmarkManager
12294 	 * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
12295 	 */
12296 	function BookmarkManager(selection) {
12297 		var dom = selection.dom;
12298 
12299 		/**
12300 		 * Returns a bookmark location for the current selection. This bookmark object
12301 		 * can then be used to restore the selection after some content modification to the document.
12302 		 *
12303 		 * @method getBookmark
12304 		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
12305 		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
12306 		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
12307 		 * @example
12308 		 * // Stores a bookmark of the current selection
12309 		 * var bm = tinymce.activeEditor.selection.getBookmark();
12310 		 *
12311 		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12312 		 *
12313 		 * // Restore the selection bookmark
12314 		 * tinymce.activeEditor.selection.moveToBookmark(bm);
12315 		 */
12316 		this.getBookmark = function(type, normalized) {
12317 			var rng, rng2, id, collapsed, name, element, chr = '', styles;
12318 
12319 			function findIndex(name, element) {
12320 				var index = 0;
12321 
12322 				Tools.each(dom.select(name), function(node, i) {
12323 					if (node == element) {
12324 						index = i;
12325 					}
12326 				});
12327 
12328 				return index;
12329 			}
12330 
12331 			function normalizeTableCellSelection(rng) {
12332 				function moveEndPoint(start) {
12333 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
12334 
12335 					container = rng[prefix + 'Container'];
12336 					offset = rng[prefix + 'Offset'];
12337 
12338 					if (container.nodeType == 1 && container.nodeName == "TR") {
12339 						childNodes = container.childNodes;
12340 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
12341 						if (container) {
12342 							offset = start ? 0 : container.childNodes.length;
12343 							rng['set' + (start ? 'Start' : 'End')](container, offset);
12344 						}
12345 					}
12346 				}
12347 
12348 				moveEndPoint(true);
12349 				moveEndPoint();
12350 
12351 				return rng;
12352 			}
12353 
12354 			function getLocation() {
12355 				var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
12356 
12357 				function getPoint(rng, start) {
12358 					var container = rng[start ? 'startContainer' : 'endContainer'],
12359 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
12360 
12361 					if (container.nodeType == 3) {
12362 						if (normalized) {
12363 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
12364 								offset += node.nodeValue.length;
12365 							}
12366 						}
12367 
12368 						point.push(offset);
12369 					} else {
12370 						childNodes = container.childNodes;
12371 
12372 						if (offset >= childNodes.length && childNodes.length) {
12373 							after = 1;
12374 							offset = Math.max(0, childNodes.length - 1);
12375 						}
12376 
12377 						point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
12378 					}
12379 
12380 					for (; container && container != root; container = container.parentNode) {
12381 						point.push(dom.nodeIndex(container, normalized));
12382 					}
12383 
12384 					return point;
12385 				}
12386 
12387 				bookmark.start = getPoint(rng, true);
12388 
12389 				if (!selection.isCollapsed()) {
12390 					bookmark.end = getPoint(rng);
12391 				}
12392 
12393 				return bookmark;
12394 			}
12395 
12396 			if (type == 2) {
12397 				element = selection.getNode();
12398 				name = element ? element.nodeName : null;
12399 
12400 				if (name == 'IMG') {
12401 					return {name: name, index: findIndex(name, element)};
12402 				}
12403 
12404 				if (selection.tridentSel) {
12405 					return selection.tridentSel.getBookmark(type);
12406 				}
12407 
12408 				return getLocation();
12409 			}
12410 
12411 			// Handle simple range
12412 			if (type) {
12413 				return {rng: selection.getRng()};
12414 			}
12415 
12416 			rng = selection.getRng();
12417 			id = dom.uniqueId();
12418 			collapsed = selection.isCollapsed();
12419 			styles = 'overflow:hidden;line-height:0px';
12420 
12421 			// Explorer method
12422 			if (rng.duplicate || rng.item) {
12423 				// Text selection
12424 				if (!rng.item) {
12425 					rng2 = rng.duplicate();
12426 
12427 					try {
12428 						// Insert start marker
12429 						rng.collapse();
12430 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
12431 
12432 						// Insert end marker
12433 						if (!collapsed) {
12434 							rng2.collapse(false);
12435 
12436 							// Detect the empty space after block elements in IE and move the
12437 							// end back one character <p></p>] becomes <p>]</p>
12438 							rng.moveToElementText(rng2.parentElement());
12439 							if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
12440 								rng2.move('character', -1);
12441 							}
12442 
12443 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
12444 						}
12445 					} catch (ex) {
12446 						// IE might throw unspecified error so lets ignore it
12447 						return null;
12448 					}
12449 				} else {
12450 					// Control selection
12451 					element = rng.item(0);
12452 					name = element.nodeName;
12453 
12454 					return {name: name, index: findIndex(name, element)};
12455 				}
12456 			} else {
12457 				element = selection.getNode();
12458 				name = element.nodeName;
12459 				if (name == 'IMG') {
12460 					return {name: name, index: findIndex(name, element)};
12461 				}
12462 
12463 				// W3C method
12464 				rng2 = normalizeTableCellSelection(rng.cloneRange());
12465 
12466 				// Insert end marker
12467 				if (!collapsed) {
12468 					rng2.collapse(false);
12469 					rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
12470 				}
12471 
12472 				rng = normalizeTableCellSelection(rng);
12473 				rng.collapse(true);
12474 				rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
12475 			}
12476 
12477 			selection.moveToBookmark({id: id, keep: 1});
12478 
12479 			return {id: id};
12480 		};
12481 
12482 		/**
12483 		 * Restores the selection to the specified bookmark.
12484 		 *
12485 		 * @method moveToBookmark
12486 		 * @param {Object} bookmark Bookmark to restore selection from.
12487 		 * @return {Boolean} true/false if it was successful or not.
12488 		 * @example
12489 		 * // Stores a bookmark of the current selection
12490 		 * var bm = tinymce.activeEditor.selection.getBookmark();
12491 		 *
12492 		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12493 		 *
12494 		 * // Restore the selection bookmark
12495 		 * tinymce.activeEditor.selection.moveToBookmark(bm);
12496 		 */
12497 		this.moveToBookmark = function(bookmark) {
12498 			var rng, root, startContainer, endContainer, startOffset, endOffset;
12499 
12500 			function setEndPoint(start) {
12501 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
12502 
12503 				if (point) {
12504 					offset = point[0];
12505 
12506 					// Find container node
12507 					for (node = root, i = point.length - 1; i >= 1; i--) {
12508 						children = node.childNodes;
12509 
12510 						if (point[i] > children.length - 1) {
12511 							return;
12512 						}
12513 
12514 						node = children[point[i]];
12515 					}
12516 
12517 					// Move text offset to best suitable location
12518 					if (node.nodeType === 3) {
12519 						offset = Math.min(point[0], node.nodeValue.length);
12520 					}
12521 
12522 					// Move element offset to best suitable location
12523 					if (node.nodeType === 1) {
12524 						offset = Math.min(point[0], node.childNodes.length);
12525 					}
12526 
12527 					// Set offset within container node
12528 					if (start) {
12529 						rng.setStart(node, offset);
12530 					} else {
12531 						rng.setEnd(node, offset);
12532 					}
12533 				}
12534 
12535 				return true;
12536 			}
12537 
12538 			function restoreEndPoint(suffix) {
12539 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
12540 
12541 				if (marker) {
12542 					node = marker.parentNode;
12543 
12544 					if (suffix == 'start') {
12545 						if (!keep) {
12546 							idx = dom.nodeIndex(marker);
12547 						} else {
12548 							node = marker.firstChild;
12549 							idx = 1;
12550 						}
12551 
12552 						startContainer = endContainer = node;
12553 						startOffset = endOffset = idx;
12554 					} else {
12555 						if (!keep) {
12556 							idx = dom.nodeIndex(marker);
12557 						} else {
12558 							node = marker.firstChild;
12559 							idx = 1;
12560 						}
12561 
12562 						endContainer = node;
12563 						endOffset = idx;
12564 					}
12565 
12566 					if (!keep) {
12567 						prev = marker.previousSibling;
12568 						next = marker.nextSibling;
12569 
12570 						// Remove all marker text nodes
12571 						Tools.each(Tools.grep(marker.childNodes), function(node) {
12572 							if (node.nodeType == 3) {
12573 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
12574 							}
12575 						});
12576 
12577 						// Remove marker but keep children if for example contents where inserted into the marker
12578 						// Also remove duplicated instances of the marker for example by a
12579 						// split operation or by WebKit auto split on paste feature
12580 						while ((marker = dom.get(bookmark.id + '_' + suffix))) {
12581 							dom.remove(marker, 1);
12582 						}
12583 
12584 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
12585 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
12586 						// isn't worth the effort. Sorry, Opera but it's just a fact
12587 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
12588 							idx = prev.nodeValue.length;
12589 							prev.appendData(next.nodeValue);
12590 							dom.remove(next);
12591 
12592 							if (suffix == 'start') {
12593 								startContainer = endContainer = prev;
12594 								startOffset = endOffset = idx;
12595 							} else {
12596 								endContainer = prev;
12597 								endOffset = idx;
12598 							}
12599 						}
12600 					}
12601 				}
12602 			}
12603 
12604 			function addBogus(node) {
12605 				// Adds a bogus BR element for empty block elements
12606 				if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
12607 					node.innerHTML = '<br data-mce-bogus="1" />';
12608 				}
12609 
12610 				return node;
12611 			}
12612 
12613 			if (bookmark) {
12614 				if (bookmark.start) {
12615 					rng = dom.createRng();
12616 					root = dom.getRoot();
12617 
12618 					if (selection.tridentSel) {
12619 						return selection.tridentSel.moveToBookmark(bookmark);
12620 					}
12621 
12622 					if (setEndPoint(true) && setEndPoint()) {
12623 						selection.setRng(rng);
12624 					}
12625 				} else if (bookmark.id) {
12626 					// Restore start/end points
12627 					restoreEndPoint('start');
12628 					restoreEndPoint('end');
12629 
12630 					if (startContainer) {
12631 						rng = dom.createRng();
12632 						rng.setStart(addBogus(startContainer), startOffset);
12633 						rng.setEnd(addBogus(endContainer), endOffset);
12634 						selection.setRng(rng);
12635 					}
12636 				} else if (bookmark.name) {
12637 					selection.select(dom.select(bookmark.name)[bookmark.index]);
12638 				} else if (bookmark.rng) {
12639 					selection.setRng(bookmark.rng);
12640 				}
12641 			}
12642 		};
12643 	}
12644 
12645 	/**
12646 	 * Returns true/false if the specified node is a bookmark node or not.
12647 	 *
12648 	 * @static
12649 	 * @method isBookmarkNode
12650 	 * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
12651 	 * @return {Boolean} true/false if the node is a bookmark node or not.
12652 	 */
12653 	BookmarkManager.isBookmarkNode = function(node) {
12654 		return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
12655 	};
12656 
12657 	return BookmarkManager;
12658 });
12659 
12660 // Included from: js/tinymce/classes/dom/Selection.js
12661 
12662 /**
12663  * Selection.js
12664  *
12665  * Copyright, Moxiecode Systems AB
12666  * Released under LGPL License.
12667  *
12668  * License: http://www.tinymce.com/license
12669  * Contributing: http://www.tinymce.com/contributing
12670  */
12671 
12672 /**
12673  * This class handles text and control selection it's an crossbrowser utility class.
12674  * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
12675  *
12676  * @class tinymce.dom.Selection
12677  * @example
12678  * // Getting the currently selected node for the active editor
12679  * alert(tinymce.activeEditor.selection.getNode().nodeName);
12680  */
12681 define("tinymce/dom/Selection", [
12682 	"tinymce/dom/TreeWalker",
12683 	"tinymce/dom/TridentSelection",
12684 	"tinymce/dom/ControlSelection",
12685 	"tinymce/dom/RangeUtils",
12686 	"tinymce/dom/BookmarkManager",
12687 	"tinymce/Env",
12688 	"tinymce/util/Tools"
12689 ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
12690 	var each = Tools.each, trim = Tools.trim;
12691 	var isIE = Env.ie;
12692 
12693 	/**
12694 	 * Constructs a new selection instance.
12695 	 *
12696 	 * @constructor
12697 	 * @method Selection
12698 	 * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
12699 	 * @param {Window} win Window to bind the selection object to.
12700 	 * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
12701 	 */
12702 	function Selection(dom, win, serializer, editor) {
12703 		var self = this;
12704 
12705 		self.dom = dom;
12706 		self.win = win;
12707 		self.serializer = serializer;
12708 		self.editor = editor;
12709 		self.bookmarkManager = new BookmarkManager(self);
12710 		self.controlSelection = new ControlSelection(self, editor);
12711 
12712 		// No W3C Range support
12713 		if (!self.win.getSelection) {
12714 			self.tridentSel = new TridentSelection(self);
12715 		}
12716 	}
12717 
12718 	Selection.prototype = {
12719 		/**
12720 		 * Move the selection cursor range to the specified node and offset.
12721 		 * If there is no node specified it will move it to the first suitable location within the body.
12722 		 *
12723 		 * @method setCursorLocation
12724 		 * @param {Node} node Optional node to put the cursor in.
12725 		 * @param {Number} offset Optional offset from the start of the node to put the cursor at.
12726 		 */
12727 		setCursorLocation: function(node, offset) {
12728 			var self = this, rng = self.dom.createRng();
12729 
12730 			if (!node) {
12731 				self._moveEndPoint(rng, self.editor.getBody(), true);
12732 				self.setRng(rng);
12733 			} else {
12734 				rng.setStart(node, offset);
12735 				rng.setEnd(node, offset);
12736 				self.setRng(rng);
12737 				self.collapse(false);
12738 			}
12739 		},
12740 
12741 		/**
12742 		 * Returns the selected contents using the DOM serializer passed in to this class.
12743 		 *
12744 		 * @method getContent
12745 		 * @param {Object} s Optional settings class with for example output format text or html.
12746 		 * @return {String} Selected contents in for example HTML format.
12747 		 * @example
12748 		 * // Alerts the currently selected contents
12749 		 * alert(tinymce.activeEditor.selection.getContent());
12750 		 *
12751 		 * // Alerts the currently selected contents as plain text
12752 		 * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
12753 		 */
12754 		getContent: function(args) {
12755 			var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
12756 			var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
12757 
12758 			args = args || {};
12759 			whiteSpaceBefore = whiteSpaceAfter = '';
12760 			args.get = true;
12761 			args.format = args.format || 'html';
12762 			args.selection = true;
12763 			self.editor.fire('BeforeGetContent', args);
12764 
12765 			if (args.format == 'text') {
12766 				return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
12767 			}
12768 
12769 			if (rng.cloneContents) {
12770 				fragment = rng.cloneContents();
12771 
12772 				if (fragment) {
12773 					tmpElm.appendChild(fragment);
12774 				}
12775 			} else if (rng.item !== undefined || rng.htmlText !== undefined) {
12776 				// IE will produce invalid markup if elements are present that
12777 				// it doesn't understand like custom elements or HTML5 elements.
12778 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
12779 				tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
12780 				tmpElm.removeChild(tmpElm.firstChild);
12781 			} else {
12782 				tmpElm.innerHTML = rng.toString();
12783 			}
12784 
12785 			// Keep whitespace before and after
12786 			if (/^\s/.test(tmpElm.innerHTML)) {
12787 				whiteSpaceBefore = ' ';
12788 			}
12789 
12790 			if (/\s+$/.test(tmpElm.innerHTML)) {
12791 				whiteSpaceAfter = ' ';
12792 			}
12793 
12794 			args.getInner = true;
12795 
12796 			args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
12797 			self.editor.fire('GetContent', args);
12798 
12799 			return args.content;
12800 		},
12801 
12802 		/**
12803 		 * Sets the current selection to the specified content. If any contents is selected it will be replaced
12804 		 * with the contents passed in to this function. If there is no selection the contents will be inserted
12805 		 * where the caret is placed in the editor/page.
12806 		 *
12807 		 * @method setContent
12808 		 * @param {String} content HTML contents to set could also be other formats depending on settings.
12809 		 * @param {Object} args Optional settings object with for example data format.
12810 		 * @example
12811 		 * // Inserts some HTML contents at the current selection
12812 		 * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
12813 		 */
12814 		setContent: function(content, args) {
12815 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
12816 
12817 			args = args || {format: 'html'};
12818 			args.set = true;
12819 			args.selection = true;
12820 			content = args.content = content;
12821 
12822 			// Dispatch before set content event
12823 			if (!args.no_events) {
12824 				self.editor.fire('BeforeSetContent', args);
12825 			}
12826 
12827 			content = args.content;
12828 
12829 			if (rng.insertNode) {
12830 				// Make caret marker since insertNode places the caret in the beginning of text after insert
12831 				content += '<span id="__caret">_</span>';
12832 
12833 				// Delete and insert new node
12834 				if (rng.startContainer == doc && rng.endContainer == doc) {
12835 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
12836 					doc.body.innerHTML = content;
12837 				} else {
12838 					rng.deleteContents();
12839 
12840 					if (doc.body.childNodes.length === 0) {
12841 						doc.body.innerHTML = content;
12842 					} else {
12843 						// createContextualFragment doesn't exists in IE 9 DOMRanges
12844 						if (rng.createContextualFragment) {
12845 							rng.insertNode(rng.createContextualFragment(content));
12846 						} else {
12847 							// Fake createContextualFragment call in IE 9
12848 							frag = doc.createDocumentFragment();
12849 							temp = doc.createElement('div');
12850 
12851 							frag.appendChild(temp);
12852 							temp.outerHTML = content;
12853 
12854 							rng.insertNode(frag);
12855 						}
12856 					}
12857 				}
12858 
12859 				// Move to caret marker
12860 				caretNode = self.dom.get('__caret');
12861 
12862 				// Make sure we wrap it compleatly, Opera fails with a simple select call
12863 				rng = doc.createRange();
12864 				rng.setStartBefore(caretNode);
12865 				rng.setEndBefore(caretNode);
12866 				self.setRng(rng);
12867 
12868 				// Remove the caret position
12869 				self.dom.remove('__caret');
12870 
12871 				try {
12872 					self.setRng(rng);
12873 				} catch (ex) {
12874 					// Might fail on Opera for some odd reason
12875 				}
12876 			} else {
12877 				if (rng.item) {
12878 					// Delete content and get caret text selection
12879 					doc.execCommand('Delete', false, null);
12880 					rng = self.getRng();
12881 				}
12882 
12883 				// Explorer removes spaces from the beginning of pasted contents
12884 				if (/^\s+/.test(content)) {
12885 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
12886 					self.dom.remove('__mce_tmp');
12887 				} else {
12888 					rng.pasteHTML(content);
12889 				}
12890 			}
12891 
12892 			// Dispatch set content event
12893 			if (!args.no_events) {
12894 				self.editor.fire('SetContent', args);
12895 			}
12896 		},
12897 
12898 		/**
12899 		 * Returns the start element of a selection range. If the start is in a text
12900 		 * node the parent element will be returned.
12901 		 *
12902 		 * @method getStart
12903 		 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
12904 		 * @return {Element} Start element of selection range.
12905 		 */
12906 		getStart: function(real) {
12907 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
12908 
12909 			if (rng.duplicate || rng.item) {
12910 				// Control selection, return first item
12911 				if (rng.item) {
12912 					return rng.item(0);
12913 				}
12914 
12915 				// Get start element
12916 				checkRng = rng.duplicate();
12917 				checkRng.collapse(1);
12918 				startElement = checkRng.parentElement();
12919 				if (startElement.ownerDocument !== self.dom.doc) {
12920 					startElement = self.dom.getRoot();
12921 				}
12922 
12923 				// Check if range parent is inside the start element, then return the inner parent element
12924 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
12925 				parentElement = node = rng.parentElement();
12926 				while ((node = node.parentNode)) {
12927 					if (node == startElement) {
12928 						startElement = parentElement;
12929 						break;
12930 					}
12931 				}
12932 
12933 				return startElement;
12934 			} else {
12935 				startElement = rng.startContainer;
12936 
12937 				if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
12938 					if (!real || !rng.collapsed) {
12939 						startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
12940 					}
12941 				}
12942 
12943 				if (startElement && startElement.nodeType == 3) {
12944 					return startElement.parentNode;
12945 				}
12946 
12947 				return startElement;
12948 			}
12949 		},
12950 
12951 		/**
12952 		 * Returns the end element of a selection range. If the end is in a text
12953 		 * node the parent element will be returned.
12954 		 *
12955 		 * @method getEnd
12956 		 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
12957 		 * @return {Element} End element of selection range.
12958 		 */
12959 		getEnd: function(real) {
12960 			var self = this, rng = self.getRng(), endElement, endOffset;
12961 
12962 			if (rng.duplicate || rng.item) {
12963 				if (rng.item) {
12964 					return rng.item(0);
12965 				}
12966 
12967 				rng = rng.duplicate();
12968 				rng.collapse(0);
12969 				endElement = rng.parentElement();
12970 				if (endElement.ownerDocument !== self.dom.doc) {
12971 					endElement = self.dom.getRoot();
12972 				}
12973 
12974 				if (endElement && endElement.nodeName == 'BODY') {
12975 					return endElement.lastChild || endElement;
12976 				}
12977 
12978 				return endElement;
12979 			} else {
12980 				endElement = rng.endContainer;
12981 				endOffset = rng.endOffset;
12982 
12983 				if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
12984 					if (!real || !rng.collapsed) {
12985 						endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
12986 					}
12987 				}
12988 
12989 				if (endElement && endElement.nodeType == 3) {
12990 					return endElement.parentNode;
12991 				}
12992 
12993 				return endElement;
12994 			}
12995 		},
12996 
12997 		/**
12998 		 * Returns a bookmark location for the current selection. This bookmark object
12999 		 * can then be used to restore the selection after some content modification to the document.
13000 		 *
13001 		 * @method getBookmark
13002 		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
13003 		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
13004 		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
13005 		 * @example
13006 		 * // Stores a bookmark of the current selection
13007 		 * var bm = tinymce.activeEditor.selection.getBookmark();
13008 		 *
13009 		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
13010 		 *
13011 		 * // Restore the selection bookmark
13012 		 * tinymce.activeEditor.selection.moveToBookmark(bm);
13013 		 */
13014 		getBookmark: function(type, normalized) {
13015 			return this.bookmarkManager.getBookmark(type, normalized);
13016 		},
13017 
13018 		/**
13019 		 * Restores the selection to the specified bookmark.
13020 		 *
13021 		 * @method moveToBookmark
13022 		 * @param {Object} bookmark Bookmark to restore selection from.
13023 		 * @return {Boolean} true/false if it was successful or not.
13024 		 * @example
13025 		 * // Stores a bookmark of the current selection
13026 		 * var bm = tinymce.activeEditor.selection.getBookmark();
13027 		 *
13028 		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
13029 		 *
13030 		 * // Restore the selection bookmark
13031 		 * tinymce.activeEditor.selection.moveToBookmark(bm);
13032 		 */
13033 		moveToBookmark: function(bookmark) {
13034 			return this.bookmarkManager.moveToBookmark(bookmark);
13035 		},
13036 
13037 		/**
13038 		 * Selects the specified element. This will place the start and end of the selection range around the element.
13039 		 *
13040 		 * @method select
13041 		 * @param {Element} node HMTL DOM element to select.
13042 		 * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
13043 		 * @return {Element} Selected element the same element as the one that got passed in.
13044 		 * @example
13045 		 * // Select the first paragraph in the active editor
13046 		 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
13047 		 */
13048 		select: function(node, content) {
13049 			var self = this, dom = self.dom, rng = dom.createRng(), idx;
13050 
13051 			// Clear stored range set by FocusManager
13052 			self.lastFocusBookmark = null;
13053 
13054 			if (node) {
13055 				if (!content && self.controlSelection.controlSelect(node)) {
13056 					return;
13057 				}
13058 
13059 				idx = dom.nodeIndex(node);
13060 				rng.setStart(node.parentNode, idx);
13061 				rng.setEnd(node.parentNode, idx + 1);
13062 
13063 				// Find first/last text node or BR element
13064 				if (content) {
13065 					self._moveEndPoint(rng, node, true);
13066 					self._moveEndPoint(rng, node);
13067 				}
13068 
13069 				self.setRng(rng);
13070 			}
13071 
13072 			return node;
13073 		},
13074 
13075 		/**
13076 		 * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
13077 		 *
13078 		 * @method isCollapsed
13079 		 * @return {Boolean} true/false state if the selection range is collapsed or not.
13080 		 * Collapsed means if it's a caret or a larger selection.
13081 		 */
13082 		isCollapsed: function() {
13083 			var self = this, rng = self.getRng(), sel = self.getSel();
13084 
13085 			if (!rng || rng.item) {
13086 				return false;
13087 			}
13088 
13089 			if (rng.compareEndPoints) {
13090 				return rng.compareEndPoints('StartToEnd', rng) === 0;
13091 			}
13092 
13093 			return !sel || rng.collapsed;
13094 		},
13095 
13096 		/**
13097 		 * Collapse the selection to start or end of range.
13098 		 *
13099 		 * @method collapse
13100 		 * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start.
13101 		 */
13102 		collapse: function(toStart) {
13103 			var self = this, rng = self.getRng(), node;
13104 
13105 			// Control range on IE
13106 			if (rng.item) {
13107 				node = rng.item(0);
13108 				rng = self.win.document.body.createTextRange();
13109 				rng.moveToElementText(node);
13110 			}
13111 
13112 			rng.collapse(!!toStart);
13113 			self.setRng(rng);
13114 		},
13115 
13116 		/**
13117 		 * Returns the browsers internal selection object.
13118 		 *
13119 		 * @method getSel
13120 		 * @return {Selection} Internal browser selection object.
13121 		 */
13122 		getSel: function() {
13123 			var win = this.win;
13124 
13125 			return win.getSelection ? win.getSelection() : win.document.selection;
13126 		},
13127 
13128 		/**
13129 		 * Returns the browsers internal range object.
13130 		 *
13131 		 * @method getRng
13132 		 * @param {Boolean} w3c Forces a compatible W3C range on IE.
13133 		 * @return {Range} Internal browser range object.
13134 		 * @see http://www.quirksmode.org/dom/range_intro.html
13135 		 * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
13136 		 */
13137 		getRng: function(w3c) {
13138 			var self = this, selection, rng, elm, doc = self.win.document, ieRng;
13139 
13140 			function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
13141 				try {
13142 					return sourceRange.compareBoundaryPoints(how, destinationRange);
13143 				} catch (ex) {
13144 					// Gecko throws wrong document exception if the range points
13145 					// to nodes that where removed from the dom #6690
13146 					// Browsers should mutate existing DOMRange instances so that they always point
13147 					// to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
13148 					// For performance reasons just return -1
13149 					return -1;
13150 				}
13151 			}
13152 
13153 			// Use last rng passed from FocusManager if it's available this enables
13154 			// calls to editor.selection.getStart() to work when caret focus is lost on IE
13155 			if (!w3c && self.lastFocusBookmark) {
13156 				var bookmark = self.lastFocusBookmark;
13157 
13158 				// Convert bookmark to range IE 11 fix
13159 				if (bookmark.startContainer) {
13160 					rng = doc.createRange();
13161 					rng.setStart(bookmark.startContainer, bookmark.startOffset);
13162 					rng.setEnd(bookmark.endContainer, bookmark.endOffset);
13163 				} else {
13164 					rng = bookmark;
13165 				}
13166 
13167 				return rng;
13168 			}
13169 
13170 			// Found tridentSel object then we need to use that one
13171 			if (w3c && self.tridentSel) {
13172 				return self.tridentSel.getRangeAt(0);
13173 			}
13174 
13175 			try {
13176 				if ((selection = self.getSel())) {
13177 					if (selection.rangeCount > 0) {
13178 						rng = selection.getRangeAt(0);
13179 					} else {
13180 						rng = selection.createRange ? selection.createRange() : doc.createRange();
13181 					}
13182 				}
13183 			} catch (ex) {
13184 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
13185 			}
13186 
13187 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
13188 			// IE 11 doesn't support the selection object so we check for that as well
13189 			if (isIE && rng && rng.setStart && doc.selection) {
13190 				try {
13191 					// IE will sometimes throw an exception here
13192 					ieRng = doc.selection.createRange();
13193 				} catch (ex) {
13194 
13195 				}
13196 
13197 				if (ieRng && ieRng.item) {
13198 					elm = ieRng.item(0);
13199 					rng = doc.createRange();
13200 					rng.setStartBefore(elm);
13201 					rng.setEndAfter(elm);
13202 				}
13203 			}
13204 
13205 			// No range found then create an empty one
13206 			// This can occur when the editor is placed in a hidden container element on Gecko
13207 			// Or on IE when there was an exception
13208 			if (!rng) {
13209 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
13210 			}
13211 
13212 			// If range is at start of document then move it to start of body
13213 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
13214 				elm = self.dom.getRoot();
13215 				rng.setStart(elm, 0);
13216 				rng.setEnd(elm, 0);
13217 			}
13218 
13219 			if (self.selectedRange && self.explicitRange) {
13220 				if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
13221 					tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
13222 					// Safari, Opera and Chrome only ever select text which causes the range to change.
13223 					// This lets us use the originally set range if the selection hasn't been changed by the user.
13224 					rng = self.explicitRange;
13225 				} else {
13226 					self.selectedRange = null;
13227 					self.explicitRange = null;
13228 				}
13229 			}
13230 
13231 			return rng;
13232 		},
13233 
13234 		/**
13235 		 * Changes the selection to the specified DOM range.
13236 		 *
13237 		 * @method setRng
13238 		 * @param {Range} rng Range to select.
13239 		 */
13240 		setRng: function(rng, forward) {
13241 			var self = this, sel;
13242 
13243 			if (!rng) {
13244 				return;
13245 			}
13246 
13247 			// Is IE specific range
13248 			if (rng.select) {
13249 				try {
13250 					rng.select();
13251 				} catch (ex) {
13252 					// Needed for some odd IE bug #1843306
13253 				}
13254 
13255 				return;
13256 			}
13257 
13258 			if (!self.tridentSel) {
13259 				sel = self.getSel();
13260 
13261 				if (sel) {
13262 					self.explicitRange = rng;
13263 
13264 					try {
13265 						sel.removeAllRanges();
13266 						sel.addRange(rng);
13267 					} catch (ex) {
13268 						// IE might throw errors here if the editor is within a hidden container and selection is changed
13269 					}
13270 
13271 					// Forward is set to false and we have an extend function
13272 					if (forward === false && sel.extend) {
13273 						sel.collapse(rng.endContainer, rng.endOffset);
13274 						sel.extend(rng.startContainer, rng.startOffset);
13275 					}
13276 
13277 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
13278 					self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
13279 				}
13280 			} else {
13281 				// Is W3C Range fake range on IE
13282 				if (rng.cloneRange) {
13283 					try {
13284 						self.tridentSel.addRange(rng);
13285 						return;
13286 					} catch (ex) {
13287 						//IE9 throws an error here if called before selection is placed in the editor
13288 					}
13289 				}
13290 			}
13291 		},
13292 
13293 		/**
13294 		 * Sets the current selection to the specified DOM element.
13295 		 *
13296 		 * @method setNode
13297 		 * @param {Element} elm Element to set as the contents of the selection.
13298 		 * @return {Element} Returns the element that got passed in.
13299 		 * @example
13300 		 * // Inserts a DOM node at current selection/caret location
13301 		 * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
13302 		 */
13303 		setNode: function(elm) {
13304 			var self = this;
13305 
13306 			self.setContent(self.dom.getOuterHTML(elm));
13307 
13308 			return elm;
13309 		},
13310 
13311 		/**
13312 		 * Returns the currently selected element or the common ancestor element for both start and end of the selection.
13313 		 *
13314 		 * @method getNode
13315 		 * @return {Element} Currently selected element or common ancestor element.
13316 		 * @example
13317 		 * // Alerts the currently selected elements node name
13318 		 * alert(tinymce.activeEditor.selection.getNode().nodeName);
13319 		 */
13320 		getNode: function() {
13321 			var self = this, rng = self.getRng(), elm;
13322 			var startContainer = rng.startContainer, endContainer = rng.endContainer;
13323 			var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();
13324 
13325 			function skipEmptyTextNodes(node, forwards) {
13326 				var orig = node;
13327 
13328 				while (node && node.nodeType === 3 && node.length === 0) {
13329 					node = forwards ? node.nextSibling : node.previousSibling;
13330 				}
13331 
13332 				return node || orig;
13333 			}
13334 
13335 			// Range maybe lost after the editor is made visible again
13336 			if (!rng) {
13337 				return root;
13338 			}
13339 
13340 			if (rng.setStart) {
13341 				elm = rng.commonAncestorContainer;
13342 
13343 				// Handle selection a image or other control like element such as anchors
13344 				if (!rng.collapsed) {
13345 					if (startContainer == endContainer) {
13346 						if (endOffset - startOffset < 2) {
13347 							if (startContainer.hasChildNodes()) {
13348 								elm = startContainer.childNodes[startOffset];
13349 							}
13350 						}
13351 					}
13352 
13353 					// If the anchor node is a element instead of a text node then return this element
13354 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
13355 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
13356 
13357 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
13358 					// This happens when you double click an underlined word in FireFox.
13359 					if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
13360 						if (startContainer.length === startOffset) {
13361 							startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
13362 						} else {
13363 							startContainer = startContainer.parentNode;
13364 						}
13365 
13366 						if (endOffset === 0) {
13367 							endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
13368 						} else {
13369 							endContainer = endContainer.parentNode;
13370 						}
13371 
13372 						if (startContainer && startContainer === endContainer) {
13373 							return startContainer;
13374 						}
13375 					}
13376 				}
13377 
13378 				if (elm && elm.nodeType == 3) {
13379 					return elm.parentNode;
13380 				}
13381 
13382 				return elm;
13383 			}
13384 
13385 			elm = rng.item ? rng.item(0) : rng.parentElement();
13386 
13387 			// IE 7 might return elements outside the iframe
13388 			if (elm.ownerDocument !== self.win.document) {
13389 				elm = root;
13390 			}
13391 
13392 			return elm;
13393 		},
13394 
13395 		getSelectedBlocks: function(startElm, endElm) {
13396 			var self = this, dom = self.dom, node, root, selectedBlocks = [];
13397 
13398 			root = dom.getRoot();
13399 			startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
13400 			endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
13401 
13402 			if (startElm && startElm != root) {
13403 				selectedBlocks.push(startElm);
13404 			}
13405 
13406 			if (startElm && endElm && startElm != endElm) {
13407 				node = startElm;
13408 
13409 				var walker = new TreeWalker(startElm, root);
13410 				while ((node = walker.next()) && node != endElm) {
13411 					if (dom.isBlock(node)) {
13412 						selectedBlocks.push(node);
13413 					}
13414 				}
13415 			}
13416 
13417 			if (endElm && startElm != endElm && endElm != root) {
13418 				selectedBlocks.push(endElm);
13419 			}
13420 
13421 			return selectedBlocks;
13422 		},
13423 
13424 		isForward: function() {
13425 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
13426 
13427 			// No support for selection direction then always return true
13428 			if (!sel || !sel.anchorNode || !sel.focusNode) {
13429 				return true;
13430 			}
13431 
13432 			anchorRange = dom.createRng();
13433 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
13434 			anchorRange.collapse(true);
13435 
13436 			focusRange = dom.createRng();
13437 			focusRange.setStart(sel.focusNode, sel.focusOffset);
13438 			focusRange.collapse(true);
13439 
13440 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
13441 		},
13442 
13443 		normalize: function() {
13444 			var self = this, rng = self.getRng();
13445 
13446 			if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
13447 				self.setRng(rng, self.isForward());
13448 			}
13449 
13450 			return rng;
13451 		},
13452 
13453 		/**
13454 		 * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
13455 		 *
13456 		 * @method selectorChanged
13457 		 * @param {String} selector CSS selector to check for.
13458 		 * @param {function} callback Callback with state and args when the selector is matches or not.
13459 		 */
13460 		selectorChanged: function(selector, callback) {
13461 			var self = this, currentSelectors;
13462 
13463 			if (!self.selectorChangedData) {
13464 				self.selectorChangedData = {};
13465 				currentSelectors = {};
13466 
13467 				self.editor.on('NodeChange', function(e) {
13468 					var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
13469 
13470 					// Check for new matching selectors
13471 					each(self.selectorChangedData, function(callbacks, selector) {
13472 						each(parents, function(node) {
13473 							if (dom.is(node, selector)) {
13474 								if (!currentSelectors[selector]) {
13475 									// Execute callbacks
13476 									each(callbacks, function(callback) {
13477 										callback(true, {node: node, selector: selector, parents: parents});
13478 									});
13479 
13480 									currentSelectors[selector] = callbacks;
13481 								}
13482 
13483 								matchedSelectors[selector] = callbacks;
13484 								return false;
13485 							}
13486 						});
13487 					});
13488 
13489 					// Check if current selectors still match
13490 					each(currentSelectors, function(callbacks, selector) {
13491 						if (!matchedSelectors[selector]) {
13492 							delete currentSelectors[selector];
13493 
13494 							each(callbacks, function(callback) {
13495 								callback(false, {node: node, selector: selector, parents: parents});
13496 							});
13497 						}
13498 					});
13499 				});
13500 			}
13501 
13502 			// Add selector listeners
13503 			if (!self.selectorChangedData[selector]) {
13504 				self.selectorChangedData[selector] = [];
13505 			}
13506 
13507 			self.selectorChangedData[selector].push(callback);
13508 
13509 			return self;
13510 		},
13511 
13512 		getScrollContainer: function() {
13513 			var scrollContainer, node = this.dom.getRoot();
13514 
13515 			while (node && node.nodeName != 'BODY') {
13516 				if (node.scrollHeight > node.clientHeight) {
13517 					scrollContainer = node;
13518 					break;
13519 				}
13520 
13521 				node = node.parentNode;
13522 			}
13523 
13524 			return scrollContainer;
13525 		},
13526 
13527 		scrollIntoView: function(elm) {
13528 			var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH;
13529 
13530 			function getPos(elm) {
13531 				var x = 0, y = 0;
13532 
13533 				var offsetParent = elm;
13534 				while (offsetParent && offsetParent.nodeType) {
13535 					x += offsetParent.offsetLeft || 0;
13536 					y += offsetParent.offsetTop || 0;
13537 					offsetParent = offsetParent.offsetParent;
13538 				}
13539 
13540 				return {x: x, y: y};
13541 			}
13542 
13543 			if (root.nodeName != 'BODY') {
13544 				var scrollContainer = self.getScrollContainer();
13545 				if (scrollContainer) {
13546 					y = getPos(elm).y - getPos(scrollContainer).y;
13547 					viewPortH = scrollContainer.clientHeight;
13548 					viewPortY = scrollContainer.scrollTop;
13549 					if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
13550 						scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
13551 					}
13552 
13553 					return;
13554 				}
13555 			}
13556 
13557 			viewPort = dom.getViewPort(self.editor.getWin());
13558 			y = dom.getPos(elm).y;
13559 			viewPortY = viewPort.y;
13560 			viewPortH = viewPort.h;
13561 			if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
13562 				self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
13563 			}
13564 		},
13565 
13566 		placeCaretAt: function(clientX, clientY) {
13567 			var doc = this.editor.getDoc(), rng, point;
13568 
13569 			if (doc.caretPositionFromPoint) {
13570 				point = doc.caretPositionFromPoint(clientX, clientY);
13571 				rng = doc.createRange();
13572 				rng.setStart(point.offsetNode, point.offset);
13573 				rng.collapse(true);
13574 			} else if (doc.caretRangeFromPoint) {
13575 				rng = doc.caretRangeFromPoint(clientX, clientY);
13576 			} else if (doc.body.createTextRange) {
13577 				rng = doc.body.createTextRange();
13578 
13579 				try {
13580 					rng.moveToPoint(clientX, clientY);
13581 					rng.collapse(true);
13582 				} catch (ex) {
13583 					rng.collapse(clientY < doc.body.clientHeight);
13584 				}
13585 			}
13586 
13587 			this.setRng(rng);
13588 		},
13589 
13590 		_moveEndPoint: function(rng, node, start) {
13591 			var root = node, walker = new TreeWalker(node, root);
13592 			var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
13593 
13594 			do {
13595 				// Text node
13596 				if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
13597 					if (start) {
13598 						rng.setStart(node, 0);
13599 					} else {
13600 						rng.setEnd(node, node.nodeValue.length);
13601 					}
13602 
13603 					return;
13604 				}
13605 
13606 				// BR/IMG/INPUT elements but not table cells
13607 				if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) {
13608 					if (start) {
13609 						rng.setStartBefore(node);
13610 					} else {
13611 						if (node.nodeName == 'BR') {
13612 							rng.setEndBefore(node);
13613 						} else {
13614 							rng.setEndAfter(node);
13615 						}
13616 					}
13617 
13618 					return;
13619 				}
13620 
13621 				// Found empty text block old IE can place the selection inside those
13622 				if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
13623 					if (start) {
13624 						rng.setStart(node, 0);
13625 					} else {
13626 						rng.setEnd(node, 0);
13627 					}
13628 
13629 					return;
13630 				}
13631 			} while ((node = (start ? walker.next() : walker.prev())));
13632 
13633 			// Failed to find any text node or other suitable location then move to the root of body
13634 			if (root.nodeName == 'BODY') {
13635 				if (start) {
13636 					rng.setStart(root, 0);
13637 				} else {
13638 					rng.setEnd(root, root.childNodes.length);
13639 				}
13640 			}
13641 		},
13642 
13643 		destroy: function() {
13644 			this.win = null;
13645 			this.controlSelection.destroy();
13646 		}
13647 	};
13648 
13649 	return Selection;
13650 });
13651 
13652 // Included from: js/tinymce/classes/dom/ElementUtils.js
13653 
13654 /**
13655  * ElementUtils.js
13656  *
13657  * Copyright, Moxiecode Systems AB
13658  * Released under LGPL License.
13659  *
13660  * License: http://www.tinymce.com/license
13661  * Contributing: http://www.tinymce.com/contributing
13662  */
13663 
13664 /**
13665  * Utility class for various element specific functions.
13666  *
13667  * @private
13668  */
13669 define("tinymce/dom/ElementUtils", [
13670 	"tinymce/dom/BookmarkManager",
13671 	"tinymce/util/Tools"
13672 ], function(BookmarkManager, Tools) {
13673 	var each = Tools.each;
13674 
13675 	function ElementUtils(dom) {
13676 		/**
13677 		 * Compares two nodes and checks if it's attributes and styles matches.
13678 		 * This doesn't compare classes as items since their order is significant.
13679 		 *
13680 		 * @method compare
13681 		 * @param {Node} node1 First node to compare with.
13682 		 * @param {Node} node2 Second node to compare with.
13683 		 * @return {boolean} True/false if the nodes are the same or not.
13684 		 */
13685 		this.compare = function(node1, node2) {
13686 			// Not the same name
13687 			if (node1.nodeName != node2.nodeName) {
13688 				return false;
13689 			}
13690 
13691 			/**
13692 			 * Returns all the nodes attributes excluding internal ones, styles and classes.
13693 			 *
13694 			 * @private
13695 			 * @param {Node} node Node to get attributes from.
13696 			 * @return {Object} Name/value object with attributes and attribute values.
13697 			 */
13698 			function getAttribs(node) {
13699 				var attribs = {};
13700 
13701 				each(dom.getAttribs(node), function(attr) {
13702 					var name = attr.nodeName.toLowerCase();
13703 
13704 					// Don't compare internal attributes or style
13705 					if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
13706 						attribs[name] = dom.getAttrib(node, name);
13707 					}
13708 				});
13709 
13710 				return attribs;
13711 			}
13712 
13713 			/**
13714 			 * Compares two objects checks if it's key + value exists in the other one.
13715 			 *
13716 			 * @private
13717 			 * @param {Object} obj1 First object to compare.
13718 			 * @param {Object} obj2 Second object to compare.
13719 			 * @return {boolean} True/false if the objects matches or not.
13720 			 */
13721 			function compareObjects(obj1, obj2) {
13722 				var value, name;
13723 
13724 				for (name in obj1) {
13725 					// Obj1 has item obj2 doesn't have
13726 					if (obj1.hasOwnProperty(name)) {
13727 						value = obj2[name];
13728 
13729 						// Obj2 doesn't have obj1 item
13730 						if (typeof value == "undefined") {
13731 							return false;
13732 						}
13733 
13734 						// Obj2 item has a different value
13735 						if (obj1[name] != value) {
13736 							return false;
13737 						}
13738 
13739 						// Delete similar value
13740 						delete obj2[name];
13741 					}
13742 				}
13743 
13744 				// Check if obj 2 has something obj 1 doesn't have
13745 				for (name in obj2) {
13746 					// Obj2 has item obj1 doesn't have
13747 					if (obj2.hasOwnProperty(name)) {
13748 						return false;
13749 					}
13750 				}
13751 
13752 				return true;
13753 			}
13754 
13755 			// Attribs are not the same
13756 			if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
13757 				return false;
13758 			}
13759 
13760 			// Styles are not the same
13761 			if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
13762 				return false;
13763 			}
13764 
13765 			return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
13766 		};
13767 	}
13768 
13769 	return ElementUtils;
13770 });
13771 
13772 // Included from: js/tinymce/classes/fmt/Preview.js
13773 
13774 /**
13775  * Preview.js
13776  *
13777  * Copyright, Moxiecode Systems AB
13778  * Released under LGPL License.
13779  *
13780  * License: http://www.tinymce.com/license
13781  * Contributing: http://www.tinymce.com/contributing
13782  */
13783 
13784 /**
13785  * Internal class for generating previews styles for formats.
13786  *
13787  * Example:
13788  *  Preview.getCssText(editor, 'bold');
13789  *
13790  * @class tinymce.fmt.Preview
13791  * @private
13792  */
13793 define("tinymce/fmt/Preview", [
13794 	"tinymce/util/Tools"
13795 ], function(Tools) {
13796 	var each = Tools.each;
13797 
13798 	function getCssText(editor, format) {
13799 		var name, previewElm, dom = editor.dom;
13800 		var previewCss = '', parentFontSize, previewStyles;
13801 
13802 		previewStyles = editor.settings.preview_styles;
13803 
13804 		// No preview forced
13805 		if (previewStyles === false) {
13806 			return '';
13807 		}
13808 
13809 		// Default preview
13810 		if (!previewStyles) {
13811 			previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
13812 				'text-transform color background-color border border-radius outline text-shadow';
13813 		}
13814 
13815 		// Removes any variables since these can't be previewed
13816 		function removeVars(val) {
13817 			return val.replace(/%(\w+)/g, '');
13818 		}
13819 
13820 		// Create block/inline element to use for preview
13821 		if (typeof(format) == "string") {
13822 			format = editor.formatter.get(format);
13823 			if (!format) {
13824 				return;
13825 			}
13826 
13827 			format = format[0];
13828 		}
13829 
13830 		name = format.block || format.inline || 'span';
13831 		previewElm = dom.create(name);
13832 
13833 		// Add format styles to preview element
13834 		each(format.styles, function(value, name) {
13835 			value = removeVars(value);
13836 
13837 			if (value) {
13838 				dom.setStyle(previewElm, name, value);
13839 			}
13840 		});
13841 
13842 		// Add attributes to preview element
13843 		each(format.attributes, function(value, name) {
13844 			value = removeVars(value);
13845 
13846 			if (value) {
13847 				dom.setAttrib(previewElm, name, value);
13848 			}
13849 		});
13850 
13851 		// Add classes to preview element
13852 		each(format.classes, function(value) {
13853 			value = removeVars(value);
13854 
13855 			if (!dom.hasClass(previewElm, value)) {
13856 				dom.addClass(previewElm, value);
13857 			}
13858 		});
13859 
13860 		editor.fire('PreviewFormats');
13861 
13862 		// Add the previewElm outside the visual area
13863 		dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
13864 		editor.getBody().appendChild(previewElm);
13865 
13866 		// Get parent container font size so we can compute px values out of em/% for older IE:s
13867 		parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
13868 		parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
13869 
13870 		each(previewStyles.split(' '), function(name) {
13871 			var value = dom.getStyle(previewElm, name, true);
13872 
13873 			// If background is transparent then check if the body has a background color we can use
13874 			if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
13875 				value = dom.getStyle(editor.getBody(), name, true);
13876 
13877 				// Ignore white since it's the default color, not the nicest fix
13878 				// TODO: Fix this by detecting runtime style
13879 				if (dom.toHex(value).toLowerCase() == '#ffffff') {
13880 					return;
13881 				}
13882 			}
13883 
13884 			if (name == 'color') {
13885 				// Ignore black since it's the default color, not the nicest fix
13886 				// TODO: Fix this by detecting runtime style
13887 				if (dom.toHex(value).toLowerCase() == '#000000') {
13888 					return;
13889 				}
13890 			}
13891 
13892 			// Old IE won't calculate the font size so we need to do that manually
13893 			if (name == 'font-size') {
13894 				if (/em|%$/.test(value)) {
13895 					if (parentFontSize === 0) {
13896 						return;
13897 					}
13898 
13899 					// Convert font size from em/% to px
13900 					value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
13901 					value = (value * parentFontSize) + 'px';
13902 				}
13903 			}
13904 
13905 			if (name == "border" && value) {
13906 				previewCss += 'padding:0 2px;';
13907 			}
13908 
13909 			previewCss += name + ':' + value + ';';
13910 		});
13911 
13912 		editor.fire('AfterPreviewFormats');
13913 
13914 		//previewCss += 'line-height:normal';
13915 
13916 		dom.remove(previewElm);
13917 
13918 		return previewCss;
13919 	}
13920 
13921 	return {
13922 		getCssText: getCssText
13923 	};
13924 });
13925 
13926 // Included from: js/tinymce/classes/Formatter.js
13927 
13928 /**
13929  * Formatter.js
13930  *
13931  * Copyright, Moxiecode Systems AB
13932  * Released under LGPL License.
13933  *
13934  * License: http://www.tinymce.com/license
13935  * Contributing: http://www.tinymce.com/contributing
13936  */
13937 
13938 /**
13939  * Text formatter engine class. This class is used to apply formats like bold, italic, font size
13940  * etc to the current selection or specific nodes. This engine was build to replace the browsers
13941  * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
13942  *
13943  * @class tinymce.Formatter
13944  * @example
13945  *  tinymce.activeEditor.formatter.register('mycustomformat', {
13946  *    inline: 'span',
13947  *    styles: {color: '#ff0000'}
13948  *  });
13949  *
13950  *  tinymce.activeEditor.formatter.apply('mycustomformat');
13951  */
13952 define("tinymce/Formatter", [
13953 	"tinymce/dom/TreeWalker",
13954 	"tinymce/dom/RangeUtils",
13955 	"tinymce/dom/BookmarkManager",
13956 	"tinymce/dom/ElementUtils",
13957 	"tinymce/util/Tools",
13958 	"tinymce/fmt/Preview"
13959 ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
13960 	/**
13961 	 * Constructs a new formatter instance.
13962 	 *
13963 	 * @constructor Formatter
13964 	 * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
13965 	 */
13966 	return function(ed) {
13967 		var formats = {},
13968 			dom = ed.dom,
13969 			selection = ed.selection,
13970 			rangeUtils = new RangeUtils(dom),
13971 			isValid = ed.schema.isValidChild,
13972 			isBlock = dom.isBlock,
13973 			forcedRootBlock = ed.settings.forced_root_block,
13974 			nodeIndex = dom.nodeIndex,
13975 			INVISIBLE_CHAR = '\uFEFF',
13976 			MCE_ATTR_RE = /^(src|href|style)$/,
13977 			FALSE = false,
13978 			TRUE = true,
13979 			formatChangeData,
13980 			undef,
13981 			getContentEditable = dom.getContentEditable,
13982 			disableCaretContainer,
13983 			markCaretContainersBogus,
13984 			isBookmarkNode = BookmarkManager.isBookmarkNode;
13985 
13986 		var each = Tools.each,
13987 			grep = Tools.grep,
13988 			walk = Tools.walk,
13989 			extend = Tools.extend;
13990 
13991 		function isTextBlock(name) {
13992 			if (name.nodeType) {
13993 				name = name.nodeName;
13994 			}
13995 
13996 			return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
13997 		}
13998 
13999 		function getParents(node, selector) {
14000 			return dom.getParents(node, selector, dom.getRoot());
14001 		}
14002 
14003 		function isCaretNode(node) {
14004 			return node.nodeType === 1 && node.id === '_mce_caret';
14005 		}
14006 
14007 		function defaultFormats() {
14008 			register({
14009 				valigntop: [
14010 					{selector: 'td,th', styles: {'verticalAlign': 'top'}}
14011 				],
14012 
14013 				valignmiddle: [
14014 					{selector: 'td,th', styles: {'verticalAlign': 'middle'}}
14015 				],
14016 
14017 				valignbottom: [
14018 					{selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
14019 				],
14020 
14021 				alignleft: [
14022 					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
14023 					{selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
14024 				],
14025 
14026 				aligncenter: [
14027 					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
14028 					{selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
14029 					{selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
14030 				],
14031 
14032 				alignright: [
14033 					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
14034 					{selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
14035 				],
14036 
14037 				alignjustify: [
14038 					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
14039 				],
14040 
14041 				bold: [
14042 					{inline: 'strong', remove: 'all'},
14043 					{inline: 'span', styles: {fontWeight: 'bold'}},
14044 					{inline: 'b', remove: 'all'}
14045 				],
14046 
14047 				italic: [
14048 					{inline: 'em', remove: 'all'},
14049 					{inline: 'span', styles: {fontStyle: 'italic'}},
14050 					{inline: 'i', remove: 'all'}
14051 				],
14052 
14053 				underline: [
14054 					{inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
14055 					{inline: 'u', remove: 'all'}
14056 				],
14057 
14058 				strikethrough: [
14059 					{inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
14060 					{inline: 'strike', remove: 'all'}
14061 				],
14062 
14063 				forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
14064 				hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
14065 				fontname: {inline: 'span', styles: {fontFamily: '%value'}},
14066 				fontsize: {inline: 'span', styles: {fontSize: '%value'}},
14067 				fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
14068 				blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
14069 				subscript: {inline: 'sub'},
14070 				superscript: {inline: 'sup'},
14071 				code: {inline: 'code'},
14072 
14073 				link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
14074 					onmatch: function() {
14075 						return true;
14076 					},
14077 
14078 					onformat: function(elm, fmt, vars) {
14079 						each(vars, function(value, key) {
14080 							dom.setAttrib(elm, key, value);
14081 						});
14082 					}
14083 				},
14084 
14085 				removeformat: [
14086 					{
14087 						selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
14088 						remove: 'all',
14089 						split: true,
14090 						expand: false,
14091 						block_expand: true,
14092 						deep: true
14093 					},
14094 					{selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
14095 					{selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
14096 				]
14097 			});
14098 
14099 			// Register default block formats
14100 			each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
14101 				register(name, {block: name, remove: 'all'});
14102 			});
14103 
14104 			// Register user defined formats
14105 			register(ed.settings.formats);
14106 		}
14107 
14108 		function addKeyboardShortcuts() {
14109 			// Add some inline shortcuts
14110 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
14111 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
14112 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
14113 
14114 			// BlockFormat shortcuts keys
14115 			for (var i = 1; i <= 6; i++) {
14116 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
14117 			}
14118 
14119 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
14120 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
14121 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
14122 		}
14123 
14124 		// Public functions
14125 
14126 		/**
14127 		 * Returns the format by name or all formats if no name is specified.
14128 		 *
14129 		 * @method get
14130 		 * @param {String} name Optional name to retrive by.
14131 		 * @return {Array/Object} Array/Object with all registred formats or a specific format.
14132 		 */
14133 		function get(name) {
14134 			return name ? formats[name] : formats;
14135 		}
14136 
14137 		/**
14138 		 * Registers a specific format by name.
14139 		 *
14140 		 * @method register
14141 		 * @param {Object/String} name Name of the format for example "bold".
14142 		 * @param {Object/Array} format Optional format object or array of format variants
14143 		 * can only be omitted if the first arg is an object.
14144 		 */
14145 		function register(name, format) {
14146 			if (name) {
14147 				if (typeof(name) !== 'string') {
14148 					each(name, function(format, name) {
14149 						register(name, format);
14150 					});
14151 				} else {
14152 					// Force format into array and add it to internal collection
14153 					format = format.length ? format : [format];
14154 
14155 					each(format, function(format) {
14156 						// Set deep to false by default on selector formats this to avoid removing
14157 						// alignment on images inside paragraphs when alignment is changed on paragraphs
14158 						if (format.deep === undef) {
14159 							format.deep = !format.selector;
14160 						}
14161 
14162 						// Default to true
14163 						if (format.split === undef) {
14164 							format.split = !format.selector || format.inline;
14165 						}
14166 
14167 						// Default to true
14168 						if (format.remove === undef && format.selector && !format.inline) {
14169 							format.remove = 'none';
14170 						}
14171 
14172 						// Mark format as a mixed format inline + block level
14173 						if (format.selector && format.inline) {
14174 							format.mixed = true;
14175 							format.block_expand = true;
14176 						}
14177 
14178 						// Split classes if needed
14179 						if (typeof(format.classes) === 'string') {
14180 							format.classes = format.classes.split(/\s+/);
14181 						}
14182 					});
14183 
14184 					formats[name] = format;
14185 				}
14186 			}
14187 		}
14188 
14189 		/**
14190 		 * Unregister a specific format by name.
14191 		 *
14192 		 * @method unregister
14193 		 * @param {String} name Name of the format for example "bold".
14194 		 */
14195 		function unregister(name) {
14196 			if (name && formats[name]) {
14197 				delete formats[name];
14198 			}
14199 
14200 			return formats;
14201 		}
14202 
14203 		function getTextDecoration(node) {
14204 			var decoration;
14205 
14206 			ed.dom.getParent(node, function(n) {
14207 				decoration = ed.dom.getStyle(n, 'text-decoration');
14208 				return decoration && decoration !== 'none';
14209 			});
14210 
14211 			return decoration;
14212 		}
14213 
14214 		function processUnderlineAndColor(node) {
14215 			var textDecoration;
14216 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14217 				textDecoration = getTextDecoration(node.parentNode);
14218 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
14219 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
14220 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14221 					ed.dom.setStyle(node, 'text-decoration', null);
14222 				}
14223 			}
14224 		}
14225 
14226 		/**
14227 		 * Applies the specified format to the current selection or specified node.
14228 		 *
14229 		 * @method apply
14230 		 * @param {String} name Name of format to apply.
14231 		 * @param {Object} vars Optional list of variables to replace within format before applying it.
14232 		 * @param {Node} node Optional node to apply the format to defaults to current selection.
14233 		 */
14234 		function apply(name, vars, node) {
14235 			var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
14236 
14237 			function setElementFormat(elm, fmt) {
14238 				fmt = fmt || format;
14239 
14240 				if (elm) {
14241 					if (fmt.onformat) {
14242 						fmt.onformat(elm, fmt, vars, node);
14243 					}
14244 
14245 					each(fmt.styles, function(value, name) {
14246 						dom.setStyle(elm, name, replaceVars(value, vars));
14247 					});
14248 
14249 					// Needed for the WebKit span spam bug
14250 					// TODO: Remove this once WebKit/Blink fixes this
14251 					if (fmt.styles) {
14252 						var styleVal = dom.getAttrib(elm, 'style');
14253 
14254 						if (styleVal) {
14255 							elm.setAttribute('data-mce-style', styleVal);
14256 						}
14257 					}
14258 
14259 					each(fmt.attributes, function(value, name) {
14260 						dom.setAttrib(elm, name, replaceVars(value, vars));
14261 					});
14262 
14263 					each(fmt.classes, function(value) {
14264 						value = replaceVars(value, vars);
14265 
14266 						if (!dom.hasClass(elm, value)) {
14267 							dom.addClass(elm, value);
14268 						}
14269 					});
14270 				}
14271 			}
14272 
14273 			function adjustSelectionToVisibleSelection() {
14274 				function findSelectionEnd(start, end) {
14275 					var walker = new TreeWalker(end);
14276 					for (node = walker.current(); node; node = walker.prev()) {
14277 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
14278 							return node;
14279 						}
14280 					}
14281 				}
14282 
14283 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
14284 				// as this isn't visible to the user.
14285 				var rng = ed.selection.getRng();
14286 				var start = rng.startContainer;
14287 				var end = rng.endContainer;
14288 
14289 				if (start != end && rng.endOffset === 0) {
14290 					var newEnd = findSelectionEnd(start, end);
14291 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14292 
14293 					rng.setEnd(newEnd, endOffset);
14294 				}
14295 
14296 				return rng;
14297 			}
14298 
14299 			function applyRngStyle(rng, bookmark, node_specific) {
14300 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
14301 
14302 				// Setup wrapper element
14303 				wrapName = format.inline || format.block;
14304 				wrapElm = dom.create(wrapName);
14305 				setElementFormat(wrapElm);
14306 
14307 				rangeUtils.walk(rng, function(nodes) {
14308 					var currentWrapElm;
14309 
14310 					/**
14311 					 * Process a list of nodes wrap them.
14312 					 */
14313 					function process(node) {
14314 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
14315 
14316 						lastContentEditable = contentEditable;
14317 						nodeName = node.nodeName.toLowerCase();
14318 						parentName = node.parentNode.nodeName.toLowerCase();
14319 
14320 						// Node has a contentEditable value
14321 						if (node.nodeType === 1 && getContentEditable(node)) {
14322 							lastContentEditable = contentEditable;
14323 							contentEditable = getContentEditable(node) === "true";
14324 							hasContentEditableState = true; // We don't want to wrap the container only it's children
14325 						}
14326 
14327 						// Stop wrapping on br elements
14328 						if (isEq(nodeName, 'br')) {
14329 							currentWrapElm = 0;
14330 
14331 							// Remove any br elements when we wrap things
14332 							if (format.block) {
14333 								dom.remove(node);
14334 							}
14335 
14336 							return;
14337 						}
14338 
14339 						// If node is wrapper type
14340 						if (format.wrapper && matchNode(node, name, vars)) {
14341 							currentWrapElm = 0;
14342 							return;
14343 						}
14344 
14345 						// Can we rename the block
14346 						// TODO: Break this if up, too complex
14347 						if (contentEditable && !hasContentEditableState && format.block &&
14348 							!format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
14349 							node = dom.rename(node, wrapName);
14350 							setElementFormat(node);
14351 							newWrappers.push(node);
14352 							currentWrapElm = 0;
14353 							return;
14354 						}
14355 
14356 						// Handle selector patterns
14357 						if (format.selector) {
14358 							// Look for matching formats
14359 							each(formatList, function(format) {
14360 								// Check collapsed state if it exists
14361 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
14362 									return;
14363 								}
14364 
14365 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
14366 									setElementFormat(node, format);
14367 									found = true;
14368 								}
14369 							});
14370 
14371 							// Continue processing if a selector match wasn't found and a inline element is defined
14372 							if (!format.inline || found) {
14373 								currentWrapElm = 0;
14374 								return;
14375 							}
14376 						}
14377 
14378 						// Is it valid to wrap this item
14379 						// TODO: Break this if up, too complex
14380 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14381 								!(!node_specific && node.nodeType === 3 &&
14382 								node.nodeValue.length === 1 &&
14383 								node.nodeValue.charCodeAt(0) === 65279) &&
14384 								!isCaretNode(node) &&
14385 								(!format.inline || !isBlock(node))) {
14386 							// Start wrapping
14387 							if (!currentWrapElm) {
14388 								// Wrap the node
14389 								currentWrapElm = dom.clone(wrapElm, FALSE);
14390 								node.parentNode.insertBefore(currentWrapElm, node);
14391 								newWrappers.push(currentWrapElm);
14392 							}
14393 
14394 							currentWrapElm.appendChild(node);
14395 						} else {
14396 							// Start a new wrapper for possible children
14397 							currentWrapElm = 0;
14398 
14399 							each(grep(node.childNodes), process);
14400 
14401 							if (hasContentEditableState) {
14402 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
14403 							}
14404 
14405 							// End the last wrapper
14406 							currentWrapElm = 0;
14407 						}
14408 					}
14409 
14410 					// Process siblings from range
14411 					each(nodes, process);
14412 				});
14413 
14414 				// Apply formats to links as well to get the color of the underline to change as well
14415 				if (format.links === true) {
14416 					each(newWrappers, function(node) {
14417 						function process(node) {
14418 							if (node.nodeName === 'A') {
14419 								setElementFormat(node, format);
14420 							}
14421 
14422 							each(grep(node.childNodes), process);
14423 						}
14424 
14425 						process(node);
14426 					});
14427 				}
14428 
14429 				// Cleanup
14430 				each(newWrappers, function(node) {
14431 					var childCount;
14432 
14433 					function getChildCount(node) {
14434 						var count = 0;
14435 
14436 						each(node.childNodes, function(node) {
14437 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
14438 								count++;
14439 							}
14440 						});
14441 
14442 						return count;
14443 					}
14444 
14445 					function mergeStyles(node) {
14446 						var child, clone;
14447 
14448 						each(node.childNodes, function(node) {
14449 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14450 								child = node;
14451 								return FALSE; // break loop
14452 							}
14453 						});
14454 
14455 						// If child was found and of the same type as the current node
14456 						if (child && !isBookmarkNode(child) && matchName(child, format)) {
14457 							clone = dom.clone(child, FALSE);
14458 							setElementFormat(clone);
14459 
14460 							dom.replace(clone, node, TRUE);
14461 							dom.remove(child, 1);
14462 						}
14463 
14464 						return clone || node;
14465 					}
14466 
14467 					childCount = getChildCount(node);
14468 
14469 					// Remove empty nodes but only if there is multiple wrappers and they are not block
14470 					// elements so never remove single <h1></h1> since that would remove the
14471 					// currrent empty block element where the caret is at
14472 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
14473 						dom.remove(node, 1);
14474 						return;
14475 					}
14476 
14477 					if (format.inline || format.wrapper) {
14478 						// Merges the current node with it's children of similar type to reduce the number of elements
14479 						if (!format.exact && childCount === 1) {
14480 							node = mergeStyles(node);
14481 						}
14482 
14483 						// Remove/merge children
14484 						each(formatList, function(format) {
14485 							// Merge all children of similar type will move styles from child to parent
14486 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14487 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14488 							each(dom.select(format.inline, node), function(child) {
14489 								if (isBookmarkNode(child)) {
14490 									return;
14491 								}
14492 
14493 								removeFormat(format, vars, child, format.exact ? child : null);
14494 							});
14495 						});
14496 
14497 						// Remove child if direct parent is of same type
14498 						if (matchNode(node.parentNode, name, vars)) {
14499 							dom.remove(node, 1);
14500 							node = 0;
14501 							return TRUE;
14502 						}
14503 
14504 						// Look for parent with similar style format
14505 						if (format.merge_with_parents) {
14506 							dom.getParent(node.parentNode, function(parent) {
14507 								if (matchNode(parent, name, vars)) {
14508 									dom.remove(node, 1);
14509 									node = 0;
14510 									return TRUE;
14511 								}
14512 							});
14513 						}
14514 
14515 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14516 						if (node && format.merge_siblings !== false) {
14517 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14518 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14519 						}
14520 					}
14521 				});
14522 			}
14523 
14524 			if (format) {
14525 				if (node) {
14526 					if (node.nodeType) {
14527 						rng = dom.createRng();
14528 						rng.setStartBefore(node);
14529 						rng.setEndAfter(node);
14530 						applyRngStyle(expandRng(rng, formatList), null, true);
14531 					} else {
14532 						applyRngStyle(node, null, true);
14533 					}
14534 				} else {
14535 					if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
14536 						// Obtain selection node before selection is unselected by applyRngStyle()
14537 						var curSelNode = ed.selection.getNode();
14538 
14539 						// If the formats have a default block and we can't find a parent block then
14540 						// start wrapping it with a DIV this is for forced_root_blocks: false
14541 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
14542 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
14543 							apply(formatList[0].defaultBlock);
14544 						}
14545 
14546 						// Apply formatting to selection
14547 						ed.selection.setRng(adjustSelectionToVisibleSelection());
14548 						bookmark = selection.getBookmark();
14549 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
14550 
14551 						// Colored nodes should be underlined so that the color of the underline matches the text color.
14552 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14553 							walk(curSelNode, processUnderlineAndColor, 'childNodes');
14554 							processUnderlineAndColor(curSelNode);
14555 						}
14556 
14557 						selection.moveToBookmark(bookmark);
14558 						moveStart(selection.getRng(TRUE));
14559 						ed.nodeChanged();
14560 					} else {
14561 						performCaretAction('apply', name, vars);
14562 					}
14563 				}
14564 			}
14565 		}
14566 
14567 		/**
14568 		 * Removes the specified format from the current selection or specified node.
14569 		 *
14570 		 * @method remove
14571 		 * @param {String} name Name of format to remove.
14572 		 * @param {Object} vars Optional list of variables to replace within format before removing it.
14573 		 * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
14574 		 */
14575 		function remove(name, vars, node, similar) {
14576 			var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
14577 
14578 			// Merges the styles for each node
14579 			function process(node) {
14580 				var children, i, l, lastContentEditable, hasContentEditableState;
14581 
14582 				// Node has a contentEditable value
14583 				if (node.nodeType === 1 && getContentEditable(node)) {
14584 					lastContentEditable = contentEditable;
14585 					contentEditable = getContentEditable(node) === "true";
14586 					hasContentEditableState = true; // We don't want to wrap the container only it's children
14587 				}
14588 
14589 				// Grab the children first since the nodelist might be changed
14590 				children = grep(node.childNodes);
14591 
14592 				// Process current node
14593 				if (contentEditable && !hasContentEditableState) {
14594 					for (i = 0, l = formatList.length; i < l; i++) {
14595 						if (removeFormat(formatList[i], vars, node, node)) {
14596 							break;
14597 						}
14598 					}
14599 				}
14600 
14601 				// Process the children
14602 				if (format.deep) {
14603 					if (children.length) {
14604 						for (i = 0, l = children.length; i < l; i++) {
14605 							process(children[i]);
14606 						}
14607 
14608 						if (hasContentEditableState) {
14609 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
14610 						}
14611 					}
14612 				}
14613 			}
14614 
14615 			function findFormatRoot(container) {
14616 				var formatRoot;
14617 
14618 				// Find format root
14619 				each(getParents(container.parentNode).reverse(), function(parent) {
14620 					var format;
14621 
14622 					// Find format root element
14623 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14624 						// Is the node matching the format we are looking for
14625 						format = matchNode(parent, name, vars, similar);
14626 						if (format && format.split !== false) {
14627 							formatRoot = parent;
14628 						}
14629 					}
14630 				});
14631 
14632 				return formatRoot;
14633 			}
14634 
14635 			function wrapAndSplit(formatRoot, container, target, split) {
14636 				var parent, clone, lastClone, firstClone, i, formatRootParent;
14637 
14638 				// Format root found then clone formats and split it
14639 				if (formatRoot) {
14640 					formatRootParent = formatRoot.parentNode;
14641 
14642 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14643 						clone = dom.clone(parent, FALSE);
14644 
14645 						for (i = 0; i < formatList.length; i++) {
14646 							if (removeFormat(formatList[i], vars, clone, clone)) {
14647 								clone = 0;
14648 								break;
14649 							}
14650 						}
14651 
14652 						// Build wrapper node
14653 						if (clone) {
14654 							if (lastClone) {
14655 								clone.appendChild(lastClone);
14656 							}
14657 
14658 							if (!firstClone) {
14659 								firstClone = clone;
14660 							}
14661 
14662 							lastClone = clone;
14663 						}
14664 					}
14665 
14666 					// Never split block elements if the format is mixed
14667 					if (split && (!format.mixed || !isBlock(formatRoot))) {
14668 						container = dom.split(formatRoot, container);
14669 					}
14670 
14671 					// Wrap container in cloned formats
14672 					if (lastClone) {
14673 						target.parentNode.insertBefore(lastClone, target);
14674 						firstClone.appendChild(target);
14675 					}
14676 				}
14677 
14678 				return container;
14679 			}
14680 
14681 			function splitToFormatRoot(container) {
14682 				return wrapAndSplit(findFormatRoot(container), container, container, true);
14683 			}
14684 
14685 			function unwrap(start) {
14686 				var node = dom.get(start ? '_start' : '_end'),
14687 					out = node[start ? 'firstChild' : 'lastChild'];
14688 
14689 				// If the end is placed within the start the result will be removed
14690 				// So this checks if the out node is a bookmark node if it is it
14691 				// checks for another more suitable node
14692 				if (isBookmarkNode(out)) {
14693 					out = out[start ? 'firstChild' : 'lastChild'];
14694 				}
14695 
14696 				dom.remove(node, true);
14697 
14698 				return out;
14699 			}
14700 
14701 			function removeRngStyle(rng) {
14702 				var startContainer, endContainer;
14703 				var commonAncestorContainer = rng.commonAncestorContainer;
14704 
14705 				rng = expandRng(rng, formatList, TRUE);
14706 
14707 				if (format.split) {
14708 					startContainer = getContainer(rng, TRUE);
14709 					endContainer = getContainer(rng);
14710 
14711 					if (startContainer != endContainer) {
14712 						// WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
14713 						// so let's see if we can use the first child instead
14714 						// This will happen if you triple click a table cell and use remove formatting
14715 						if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
14716 							if (startContainer.nodeName == "TR") {
14717 								startContainer = startContainer.firstChild.firstChild || startContainer;
14718 							} else {
14719 								startContainer = startContainer.firstChild || startContainer;
14720 							}
14721 						}
14722 
14723 						// Try to adjust endContainer as well if cells on the same row were selected - bug #6410
14724 						if (commonAncestorContainer &&
14725 							/^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
14726 							/^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) {
14727 							endContainer = endContainer.firstChild || endContainer;
14728 						}
14729 
14730 						// Wrap start/end nodes in span element since these might be cloned/moved
14731 						startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
14732 						endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
14733 
14734 						// Split start/end
14735 						splitToFormatRoot(startContainer);
14736 						splitToFormatRoot(endContainer);
14737 
14738 						// Unwrap start/end to get real elements again
14739 						startContainer = unwrap(TRUE);
14740 						endContainer = unwrap();
14741 					} else {
14742 						startContainer = endContainer = splitToFormatRoot(startContainer);
14743 					}
14744 
14745 					// Update range positions since they might have changed after the split operations
14746 					rng.startContainer = startContainer.parentNode;
14747 					rng.startOffset = nodeIndex(startContainer);
14748 					rng.endContainer = endContainer.parentNode;
14749 					rng.endOffset = nodeIndex(endContainer) + 1;
14750 				}
14751 
14752 				// Remove items between start/end
14753 				rangeUtils.walk(rng, function(nodes) {
14754 					each(nodes, function(node) {
14755 						process(node);
14756 
14757 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14758 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
14759 							node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14760 							removeFormat({
14761 								'deep': false,
14762 								'exact': true,
14763 								'inline': 'span',
14764 								'styles': {
14765 									'textDecoration': 'underline'
14766 								}
14767 							}, null, node);
14768 						}
14769 					});
14770 				});
14771 			}
14772 
14773 			// Handle node
14774 			if (node) {
14775 				if (node.nodeType) {
14776 					rng = dom.createRng();
14777 					rng.setStartBefore(node);
14778 					rng.setEndAfter(node);
14779 					removeRngStyle(rng);
14780 				} else {
14781 					removeRngStyle(node);
14782 				}
14783 
14784 				return;
14785 			}
14786 
14787 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
14788 				bookmark = selection.getBookmark();
14789 				removeRngStyle(selection.getRng(TRUE));
14790 				selection.moveToBookmark(bookmark);
14791 
14792 				// Check if start element still has formatting then we are at: "<b>text|</b>text"
14793 				// and need to move the start into the next text node
14794 				if (format.inline && match(name, vars, selection.getStart())) {
14795 					moveStart(selection.getRng(true));
14796 				}
14797 
14798 				ed.nodeChanged();
14799 			} else {
14800 				performCaretAction('remove', name, vars, similar);
14801 			}
14802 		}
14803 
14804 		/**
14805 		 * Toggles the specified format on/off.
14806 		 *
14807 		 * @method toggle
14808 		 * @param {String} name Name of format to apply/remove.
14809 		 * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
14810 		 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
14811 		 */
14812 		function toggle(name, vars, node) {
14813 			var fmt = get(name);
14814 
14815 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
14816 				remove(name, vars, node);
14817 			} else {
14818 				apply(name, vars, node);
14819 			}
14820 		}
14821 
14822 		/**
14823 		 * Return true/false if the specified node has the specified format.
14824 		 *
14825 		 * @method matchNode
14826 		 * @param {Node} node Node to check the format on.
14827 		 * @param {String} name Format name to check.
14828 		 * @param {Object} vars Optional list of variables to replace before checking it.
14829 		 * @param {Boolean} similar Match format that has similar properties.
14830 		 * @return {Object} Returns the format object it matches or undefined if it doesn't match.
14831 		 */
14832 		function matchNode(node, name, vars, similar) {
14833 			var formatList = get(name), format, i, classes;
14834 
14835 			function matchItems(node, format, item_name) {
14836 				var key, value, items = format[item_name], i;
14837 
14838 				// Custom match
14839 				if (format.onmatch) {
14840 					return format.onmatch(node, format, item_name);
14841 				}
14842 
14843 				// Check all items
14844 				if (items) {
14845 					// Non indexed object
14846 					if (items.length === undef) {
14847 						for (key in items) {
14848 							if (items.hasOwnProperty(key)) {
14849 								if (item_name === 'attributes') {
14850 									value = dom.getAttrib(node, key);
14851 								} else {
14852 									value = getStyle(node, key);
14853 								}
14854 
14855 								if (similar && !value && !format.exact) {
14856 									return;
14857 								}
14858 
14859 								if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
14860 									return;
14861 								}
14862 							}
14863 						}
14864 					} else {
14865 						// Only one match needed for indexed arrays
14866 						for (i = 0; i < items.length; i++) {
14867 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
14868 								return format;
14869 							}
14870 						}
14871 					}
14872 				}
14873 
14874 				return format;
14875 			}
14876 
14877 			if (formatList && node) {
14878 				// Check each format in list
14879 				for (i = 0; i < formatList.length; i++) {
14880 					format = formatList[i];
14881 
14882 					// Name name, attributes, styles and classes
14883 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14884 						// Match classes
14885 						if ((classes = format.classes)) {
14886 							for (i = 0; i < classes.length; i++) {
14887 								if (!dom.hasClass(node, classes[i])) {
14888 									return;
14889 								}
14890 							}
14891 						}
14892 
14893 						return format;
14894 					}
14895 				}
14896 			}
14897 		}
14898 
14899 		/**
14900 		 * Matches the current selection or specified node against the specified format name.
14901 		 *
14902 		 * @method match
14903 		 * @param {String} name Name of format to match.
14904 		 * @param {Object} vars Optional list of variables to replace before checking it.
14905 		 * @param {Node} node Optional node to check.
14906 		 * @return {boolean} true/false if the specified selection/node matches the format.
14907 		 */
14908 		function match(name, vars, node) {
14909 			var startNode;
14910 
14911 			function matchParents(node) {
14912 				var root = dom.getRoot();
14913 
14914 				if (node === root) {
14915 					return false;
14916 				}
14917 
14918 				// Find first node with similar format settings
14919 				node = dom.getParent(node, function(node) {
14920 					return node.parentNode === root || !!matchNode(node, name, vars, true);
14921 				});
14922 
14923 				// Do an exact check on the similar format element
14924 				return matchNode(node, name, vars);
14925 			}
14926 
14927 			// Check specified node
14928 			if (node) {
14929 				return matchParents(node);
14930 			}
14931 
14932 			// Check selected node
14933 			node = selection.getNode();
14934 			if (matchParents(node)) {
14935 				return TRUE;
14936 			}
14937 
14938 			// Check start node if it's different
14939 			startNode = selection.getStart();
14940 			if (startNode != node) {
14941 				if (matchParents(startNode)) {
14942 					return TRUE;
14943 				}
14944 			}
14945 
14946 			return FALSE;
14947 		}
14948 
14949 		/**
14950 		 * Matches the current selection against the array of formats and returns a new array with matching formats.
14951 		 *
14952 		 * @method matchAll
14953 		 * @param {Array} names Name of format to match.
14954 		 * @param {Object} vars Optional list of variables to replace before checking it.
14955 		 * @return {Array} Array with matched formats.
14956 		 */
14957 		function matchAll(names, vars) {
14958 			var startElement, matchedFormatNames = [], checkedMap = {};
14959 
14960 			// Check start of selection for formats
14961 			startElement = selection.getStart();
14962 			dom.getParent(startElement, function(node) {
14963 				var i, name;
14964 
14965 				for (i = 0; i < names.length; i++) {
14966 					name = names[i];
14967 
14968 					if (!checkedMap[name] && matchNode(node, name, vars)) {
14969 						checkedMap[name] = true;
14970 						matchedFormatNames.push(name);
14971 					}
14972 				}
14973 			}, dom.getRoot());
14974 
14975 			return matchedFormatNames;
14976 		}
14977 
14978 		/**
14979 		 * Returns true/false if the specified format can be applied to the current selection or not. It
14980 		 * will currently only check the state for selector formats, it returns true on all other format types.
14981 		 *
14982 		 * @method canApply
14983 		 * @param {String} name Name of format to check.
14984 		 * @return {boolean} true/false if the specified format can be applied to the current selection/node.
14985 		 */
14986 		function canApply(name) {
14987 			var formatList = get(name), startNode, parents, i, x, selector;
14988 
14989 			if (formatList) {
14990 				startNode = selection.getStart();
14991 				parents = getParents(startNode);
14992 
14993 				for (x = formatList.length - 1; x >= 0; x--) {
14994 					selector = formatList[x].selector;
14995 
14996 					// Format is not selector based then always return TRUE
14997 					// Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
14998 					if (!selector || formatList[x].defaultBlock) {
14999 						return TRUE;
15000 					}
15001 
15002 					for (i = parents.length - 1; i >= 0; i--) {
15003 						if (dom.is(parents[i], selector)) {
15004 							return TRUE;
15005 						}
15006 					}
15007 				}
15008 			}
15009 
15010 			return FALSE;
15011 		}
15012 
15013 		/**
15014 		 * Executes the specified callback when the current selection matches the formats or not.
15015 		 *
15016 		 * @method formatChanged
15017 		 * @param {String} formats Comma separated list of formats to check for.
15018 		 * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
15019 		 * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
15020 		 */
15021 		function formatChanged(formats, callback, similar) {
15022 			var currentFormats;
15023 
15024 			// Setup format node change logic
15025 			if (!formatChangeData) {
15026 				formatChangeData = {};
15027 				currentFormats = {};
15028 
15029 				ed.on('NodeChange', function(e) {
15030 					var parents = getParents(e.element), matchedFormats = {};
15031 
15032 					// Ignore bogus nodes like the <a> tag created by moveStart()
15033 					parents = Tools.grep(parents, function(node) {
15034 						return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
15035 					});
15036 
15037 					// Check for new formats
15038 					each(formatChangeData, function(callbacks, format) {
15039 						each(parents, function(node) {
15040 							if (matchNode(node, format, {}, callbacks.similar)) {
15041 								if (!currentFormats[format]) {
15042 									// Execute callbacks
15043 									each(callbacks, function(callback) {
15044 										callback(true, {node: node, format: format, parents: parents});
15045 									});
15046 
15047 									currentFormats[format] = callbacks;
15048 								}
15049 
15050 								matchedFormats[format] = callbacks;
15051 								return false;
15052 							}
15053 						});
15054 					});
15055 
15056 					// Check if current formats still match
15057 					each(currentFormats, function(callbacks, format) {
15058 						if (!matchedFormats[format]) {
15059 							delete currentFormats[format];
15060 
15061 							each(callbacks, function(callback) {
15062 								callback(false, {node: e.element, format: format, parents: parents});
15063 							});
15064 						}
15065 					});
15066 				});
15067 			}
15068 
15069 			// Add format listeners
15070 			each(formats.split(','), function(format) {
15071 				if (!formatChangeData[format]) {
15072 					formatChangeData[format] = [];
15073 					formatChangeData[format].similar = similar;
15074 				}
15075 
15076 				formatChangeData[format].push(callback);
15077 			});
15078 
15079 			return this;
15080 		}
15081 
15082 		/**
15083 		 * Returns a preview css text for the specified format.
15084 		 *
15085 		 * @method getCssText
15086 		 * @param {String/Object} format Format to generate preview css text for.
15087 		 * @return {String} Css text for the specified format.
15088 		 * @example
15089 		 * var cssText1 = editor.formatter.getCssText('bold');
15090 		 * var cssText2 = editor.formatter.getCssText({inline: 'b'});
15091 		 */
15092 		function getCssText(format) {
15093 			return Preview.getCssText(ed, format);
15094 		}
15095 
15096 		// Expose to public
15097 		extend(this, {
15098 			get: get,
15099 			register: register,
15100 			unregister: unregister,
15101 			apply: apply,
15102 			remove: remove,
15103 			toggle: toggle,
15104 			match: match,
15105 			matchAll: matchAll,
15106 			matchNode: matchNode,
15107 			canApply: canApply,
15108 			formatChanged: formatChanged,
15109 			getCssText: getCssText
15110 		});
15111 
15112 		// Initialize
15113 		defaultFormats();
15114 		addKeyboardShortcuts();
15115 		ed.on('BeforeGetContent', function(e) {
15116 			if (markCaretContainersBogus && e.format != 'raw') {
15117 				markCaretContainersBogus();
15118 			}
15119 		});
15120 		ed.on('mouseup keydown', function(e) {
15121 			if (disableCaretContainer) {
15122 				disableCaretContainer(e);
15123 			}
15124 		});
15125 
15126 		// Private functions
15127 
15128 		/**
15129 		 * Checks if the specified nodes name matches the format inline/block or selector.
15130 		 *
15131 		 * @private
15132 		 * @param {Node} node Node to match against the specified format.
15133 		 * @param {Object} format Format object o match with.
15134 		 * @return {boolean} true/false if the format matches.
15135 		 */
15136 		function matchName(node, format) {
15137 			// Check for inline match
15138 			if (isEq(node, format.inline)) {
15139 				return TRUE;
15140 			}
15141 
15142 			// Check for block match
15143 			if (isEq(node, format.block)) {
15144 				return TRUE;
15145 			}
15146 
15147 			// Check for selector match
15148 			if (format.selector) {
15149 				return node.nodeType == 1 && dom.is(node, format.selector);
15150 			}
15151 		}
15152 
15153 		/**
15154 		 * Compares two string/nodes regardless of their case.
15155 		 *
15156 		 * @private
15157 		 * @param {String/Node} Node or string to compare.
15158 		 * @param {String/Node} Node or string to compare.
15159 		 * @return {boolean} True/false if they match.
15160 		 */
15161 		function isEq(str1, str2) {
15162 			str1 = str1 || '';
15163 			str2 = str2 || '';
15164 
15165 			str1 = '' + (str1.nodeName || str1);
15166 			str2 = '' + (str2.nodeName || str2);
15167 
15168 			return str1.toLowerCase() == str2.toLowerCase();
15169 		}
15170 
15171 		/**
15172 		 * Returns the style by name on the specified node. This method modifies the style
15173 		 * contents to make it more easy to match. This will resolve a few browser issues.
15174 		 *
15175 		 * @private
15176 		 * @param {Node} node to get style from.
15177 		 * @param {String} name Style name to get.
15178 		 * @return {String} Style item value.
15179 		 */
15180 		function getStyle(node, name) {
15181 			return normalizeStyleValue(dom.getStyle(node, name), name);
15182 		}
15183 
15184 		/**
15185 		 * Normalize style value by name. This method modifies the style contents
15186 		 * to make it more easy to match. This will resolve a few browser issues.
15187 		 *
15188 		 * @private
15189 		 * @param {Node} node to get style from.
15190 		 * @param {String} name Style name to get.
15191 		 * @return {String} Style item value.
15192 		 */
15193 		function normalizeStyleValue(value, name) {
15194 			// Force the format to hex
15195 			if (name == 'color' || name == 'backgroundColor') {
15196 				value = dom.toHex(value);
15197 			}
15198 
15199 			// Opera will return bold as 700
15200 			if (name == 'fontWeight' && value == 700) {
15201 				value = 'bold';
15202 			}
15203 
15204 			// Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
15205 			if (name == 'fontFamily') {
15206 				value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
15207 			}
15208 
15209 			return '' + value;
15210 		}
15211 
15212 		/**
15213 		 * Replaces variables in the value. The variable format is %var.
15214 		 *
15215 		 * @private
15216 		 * @param {String} value Value to replace variables in.
15217 		 * @param {Object} vars Name/value array with variables to replace.
15218 		 * @return {String} New value with replaced variables.
15219 		 */
15220 		function replaceVars(value, vars) {
15221 			if (typeof(value) != "string") {
15222 				value = value(vars);
15223 			} else if (vars) {
15224 				value = value.replace(/%(\w+)/g, function(str, name) {
15225 					return vars[name] || str;
15226 				});
15227 			}
15228 
15229 			return value;
15230 		}
15231 
15232 		function isWhiteSpaceNode(node) {
15233 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
15234 		}
15235 
15236 		function wrap(node, name, attrs) {
15237 			var wrapper = dom.create(name, attrs);
15238 
15239 			node.parentNode.insertBefore(wrapper, node);
15240 			wrapper.appendChild(node);
15241 
15242 			return wrapper;
15243 		}
15244 
15245 		/**
15246 		 * Expands the specified range like object to depending on format.
15247 		 *
15248 		 * For example on block formats it will move the start/end position
15249 		 * to the beginning of the current block.
15250 		 *
15251 		 * @private
15252 		 * @param {Object} rng Range like object.
15253 		 * @param {Array} formats Array with formats to expand by.
15254 		 * @return {Object} Expanded range like object.
15255 		 */
15256 		function expandRng(rng, format, remove) {
15257 			var lastIdx, leaf, endPoint,
15258 				startContainer = rng.startContainer,
15259 				startOffset = rng.startOffset,
15260 				endContainer = rng.endContainer,
15261 				endOffset = rng.endOffset;
15262 
15263 			// This function walks up the tree if there is no siblings before/after the node
15264 			function findParentContainer(start) {
15265 				var container, parent, sibling, siblingName, root;
15266 
15267 				container = parent = start ? startContainer : endContainer;
15268 				siblingName = start ? 'previousSibling' : 'nextSibling';
15269 				root = dom.getRoot();
15270 
15271 				function isBogusBr(node) {
15272 					return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
15273 				}
15274 
15275 				// If it's a text node and the offset is inside the text
15276 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
15277 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
15278 						return container;
15279 					}
15280 				}
15281 
15282 				/*eslint no-constant-condition:0 */
15283 				while (true) {
15284 					// Stop expanding on block elements
15285 					if (!format[0].block_expand && isBlock(parent)) {
15286 						return parent;
15287 					}
15288 
15289 					// Walk left/right
15290 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
15291 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
15292 							return parent;
15293 						}
15294 					}
15295 
15296 					// Check if we can move up are we at root level or body level
15297 					if (parent.parentNode == root) {
15298 						container = parent;
15299 						break;
15300 					}
15301 
15302 					parent = parent.parentNode;
15303 				}
15304 
15305 				return container;
15306 			}
15307 
15308 			// This function walks down the tree to find the leaf at the selection.
15309 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15310 			function findLeaf(node, offset) {
15311 				if (offset === undef) {
15312 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15313 				}
15314 
15315 				while (node && node.hasChildNodes()) {
15316 					node = node.childNodes[offset];
15317 					if (node) {
15318 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15319 					}
15320 				}
15321 				return {node: node, offset: offset};
15322 			}
15323 
15324 			// If index based start position then resolve it
15325 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15326 				lastIdx = startContainer.childNodes.length - 1;
15327 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15328 
15329 				if (startContainer.nodeType == 3) {
15330 					startOffset = 0;
15331 				}
15332 			}
15333 
15334 			// If index based end position then resolve it
15335 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15336 				lastIdx = endContainer.childNodes.length - 1;
15337 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15338 
15339 				if (endContainer.nodeType == 3) {
15340 					endOffset = endContainer.nodeValue.length;
15341 				}
15342 			}
15343 
15344 			// Expands the node to the closes contentEditable false element if it exists
15345 			function findParentContentEditable(node) {
15346 				var parent = node;
15347 
15348 				while (parent) {
15349 					if (parent.nodeType === 1 && getContentEditable(parent)) {
15350 						return getContentEditable(parent) === "false" ? parent : node;
15351 					}
15352 
15353 					parent = parent.parentNode;
15354 				}
15355 
15356 				return node;
15357 			}
15358 
15359 			function findWordEndPoint(container, offset, start) {
15360 				var walker, node, pos, lastTextNode;
15361 
15362 				function findSpace(node, offset) {
15363 					var pos, pos2, str = node.nodeValue;
15364 
15365 					if (typeof(offset) == "undefined") {
15366 						offset = start ? str.length : 0;
15367 					}
15368 
15369 					if (start) {
15370 						pos = str.lastIndexOf(' ', offset);
15371 						pos2 = str.lastIndexOf('\u00a0', offset);
15372 						pos = pos > pos2 ? pos : pos2;
15373 
15374 						// Include the space on remove to avoid tag soup
15375 						if (pos !== -1 && !remove) {
15376 							pos++;
15377 						}
15378 					} else {
15379 						pos = str.indexOf(' ', offset);
15380 						pos2 = str.indexOf('\u00a0', offset);
15381 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
15382 					}
15383 
15384 					return pos;
15385 				}
15386 
15387 				if (container.nodeType === 3) {
15388 					pos = findSpace(container, offset);
15389 
15390 					if (pos !== -1) {
15391 						return {container: container, offset: pos};
15392 					}
15393 
15394 					lastTextNode = container;
15395 				}
15396 
15397 				// Walk the nodes inside the block
15398 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
15399 				while ((node = walker[start ? 'prev' : 'next']())) {
15400 					if (node.nodeType === 3) {
15401 						lastTextNode = node;
15402 						pos = findSpace(node);
15403 
15404 						if (pos !== -1) {
15405 							return {container: node, offset: pos};
15406 						}
15407 					} else if (isBlock(node)) {
15408 						break;
15409 					}
15410 				}
15411 
15412 				if (lastTextNode) {
15413 					if (start) {
15414 						offset = 0;
15415 					} else {
15416 						offset = lastTextNode.length;
15417 					}
15418 
15419 					return {container: lastTextNode, offset: offset};
15420 				}
15421 			}
15422 
15423 			function findSelectorEndPoint(container, sibling_name) {
15424 				var parents, i, y, curFormat;
15425 
15426 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
15427 					container = container[sibling_name];
15428 				}
15429 
15430 				parents = getParents(container);
15431 				for (i = 0; i < parents.length; i++) {
15432 					for (y = 0; y < format.length; y++) {
15433 						curFormat = format[y];
15434 
15435 						// If collapsed state is set then skip formats that doesn't match that
15436 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
15437 							continue;
15438 						}
15439 
15440 						if (dom.is(parents[i], curFormat.selector)) {
15441 							return parents[i];
15442 						}
15443 					}
15444 				}
15445 
15446 				return container;
15447 			}
15448 
15449 			function findBlockEndPoint(container, sibling_name) {
15450 				var node, root = dom.getRoot();
15451 
15452 				// Expand to block of similar type
15453 				if (!format[0].wrapper) {
15454 					node = dom.getParent(container, format[0].block, root);
15455 				}
15456 
15457 				// Expand to first wrappable block element or any block element
15458 				if (!node) {
15459 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
15460 						// Fixes #6183 where it would expand to editable parent element in inline mode
15461 						return node != root && isTextBlock(node);
15462 					});
15463 				}
15464 
15465 				// Exclude inner lists from wrapping
15466 				if (node && format[0].wrapper) {
15467 					node = getParents(node, 'ul,ol').reverse()[0] || node;
15468 				}
15469 
15470 				// Didn't find a block element look for first/last wrappable element
15471 				if (!node) {
15472 					node = container;
15473 
15474 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
15475 						node = node[sibling_name];
15476 
15477 						// Break on BR but include it will be removed later on
15478 						// we can't remove it now since we need to check if it can be wrapped
15479 						if (isEq(node, 'br')) {
15480 							break;
15481 						}
15482 					}
15483 				}
15484 
15485 				return node || container;
15486 			}
15487 
15488 			// Expand to closest contentEditable element
15489 			startContainer = findParentContentEditable(startContainer);
15490 			endContainer = findParentContentEditable(endContainer);
15491 
15492 			// Exclude bookmark nodes if possible
15493 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
15494 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
15495 				startContainer = startContainer.nextSibling || startContainer;
15496 
15497 				if (startContainer.nodeType == 3) {
15498 					startOffset = 0;
15499 				}
15500 			}
15501 
15502 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
15503 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
15504 				endContainer = endContainer.previousSibling || endContainer;
15505 
15506 				if (endContainer.nodeType == 3) {
15507 					endOffset = endContainer.length;
15508 				}
15509 			}
15510 
15511 			if (format[0].inline) {
15512 				if (rng.collapsed) {
15513 					// Expand left to closest word boundary
15514 					endPoint = findWordEndPoint(startContainer, startOffset, true);
15515 					if (endPoint) {
15516 						startContainer = endPoint.container;
15517 						startOffset = endPoint.offset;
15518 					}
15519 
15520 					// Expand right to closest word boundary
15521 					endPoint = findWordEndPoint(endContainer, endOffset);
15522 					if (endPoint) {
15523 						endContainer = endPoint.container;
15524 						endOffset = endPoint.offset;
15525 					}
15526 				}
15527 
15528 				// Avoid applying formatting to a trailing space.
15529 				leaf = findLeaf(endContainer, endOffset);
15530 				if (leaf.node) {
15531 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
15532 						leaf = findLeaf(leaf.node.previousSibling);
15533 					}
15534 
15535 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15536 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15537 
15538 						if (leaf.offset > 1) {
15539 							endContainer = leaf.node;
15540 							endContainer.splitText(leaf.offset - 1);
15541 						}
15542 					}
15543 				}
15544 			}
15545 
15546 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
15547 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15548 			// This will reduce the number of wrapper elements that needs to be created
15549 			// Move start point up the tree
15550 			if (format[0].inline || format[0].block_expand) {
15551 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
15552 					startContainer = findParentContainer(true);
15553 				}
15554 
15555 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
15556 					endContainer = findParentContainer();
15557 				}
15558 			}
15559 
15560 			// Expand start/end container to matching selector
15561 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15562 				// Find new startContainer/endContainer if there is better one
15563 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15564 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15565 			}
15566 
15567 			// Expand start/end container to matching block element or text node
15568 			if (format[0].block || format[0].selector) {
15569 				// Find new startContainer/endContainer if there is better one
15570 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15571 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15572 
15573 				// Non block element then try to expand up the leaf
15574 				if (format[0].block) {
15575 					if (!isBlock(startContainer)) {
15576 						startContainer = findParentContainer(true);
15577 					}
15578 
15579 					if (!isBlock(endContainer)) {
15580 						endContainer = findParentContainer();
15581 					}
15582 				}
15583 			}
15584 
15585 			// Setup index for startContainer
15586 			if (startContainer.nodeType == 1) {
15587 				startOffset = nodeIndex(startContainer);
15588 				startContainer = startContainer.parentNode;
15589 			}
15590 
15591 			// Setup index for endContainer
15592 			if (endContainer.nodeType == 1) {
15593 				endOffset = nodeIndex(endContainer) + 1;
15594 				endContainer = endContainer.parentNode;
15595 			}
15596 
15597 			// Return new range like object
15598 			return {
15599 				startContainer: startContainer,
15600 				startOffset: startOffset,
15601 				endContainer: endContainer,
15602 				endOffset: endOffset
15603 			};
15604 		}
15605 
15606 		function isColorFormatAndAnchor(node, format) {
15607 			return format.links && node.tagName == 'A';
15608 		}
15609 
15610 		/**
15611 		 * Removes the specified format for the specified node. It will also remove the node if it doesn't have
15612 		 * any attributes if the format specifies it to do so.
15613 		 *
15614 		 * @private
15615 		 * @param {Object} format Format object with items to remove from node.
15616 		 * @param {Object} vars Name/value object with variables to apply to format.
15617 		 * @param {Node} node Node to remove the format styles on.
15618 		 * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
15619 		 * @return {Boolean} True/false if the node was removed or not.
15620 		 */
15621 		function removeFormat(format, vars, node, compare_node) {
15622 			var i, attrs, stylesModified;
15623 
15624 			// Check if node matches format
15625 			if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
15626 				return FALSE;
15627 			}
15628 
15629 			// Should we compare with format attribs and styles
15630 			if (format.remove != 'all') {
15631 				// Remove styles
15632 				each(format.styles, function(value, name) {
15633 					value = normalizeStyleValue(replaceVars(value, vars), name);
15634 
15635 					// Indexed array
15636 					if (typeof(name) === 'number') {
15637 						name = value;
15638 						compare_node = 0;
15639 					}
15640 
15641 					if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
15642 						dom.setStyle(node, name, '');
15643 					}
15644 
15645 					stylesModified = 1;
15646 				});
15647 
15648 				// Remove style attribute if it's empty
15649 				if (stylesModified && dom.getAttrib(node, 'style') === '') {
15650 					node.removeAttribute('style');
15651 					node.removeAttribute('data-mce-style');
15652 				}
15653 
15654 				// Remove attributes
15655 				each(format.attributes, function(value, name) {
15656 					var valueOut;
15657 
15658 					value = replaceVars(value, vars);
15659 
15660 					// Indexed array
15661 					if (typeof(name) === 'number') {
15662 						name = value;
15663 						compare_node = 0;
15664 					}
15665 
15666 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15667 						// Keep internal classes
15668 						if (name == 'class') {
15669 							value = dom.getAttrib(node, name);
15670 							if (value) {
15671 								// Build new class value where everything is removed except the internal prefixed classes
15672 								valueOut = '';
15673 								each(value.split(/\s+/), function(cls) {
15674 									if (/mce\w+/.test(cls)) {
15675 										valueOut += (valueOut ? ' ' : '') + cls;
15676 									}
15677 								});
15678 
15679 								// We got some internal classes left
15680 								if (valueOut) {
15681 									dom.setAttrib(node, name, valueOut);
15682 									return;
15683 								}
15684 							}
15685 						}
15686 
15687 						// IE6 has a bug where the attribute doesn't get removed correctly
15688 						if (name == "class") {
15689 							node.removeAttribute('className');
15690 						}
15691 
15692 						// Remove mce prefixed attributes
15693 						if (MCE_ATTR_RE.test(name)) {
15694 							node.removeAttribute('data-mce-' + name);
15695 						}
15696 
15697 						node.removeAttribute(name);
15698 					}
15699 				});
15700 
15701 				// Remove classes
15702 				each(format.classes, function(value) {
15703 					value = replaceVars(value, vars);
15704 
15705 					if (!compare_node || dom.hasClass(compare_node, value)) {
15706 						dom.removeClass(node, value);
15707 					}
15708 				});
15709 
15710 				// Check for non internal attributes
15711 				attrs = dom.getAttribs(node);
15712 				for (i = 0; i < attrs.length; i++) {
15713 					if (attrs[i].nodeName.indexOf('_') !== 0) {
15714 						return FALSE;
15715 					}
15716 				}
15717 			}
15718 
15719 			// Remove the inline child if it's empty for example <b> or <span>
15720 			if (format.remove != 'none') {
15721 				removeNode(node, format);
15722 				return TRUE;
15723 			}
15724 		}
15725 
15726 		/**
15727 		 * Removes the node and wrap it's children in paragraphs before doing so or
15728 		 * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
15729 		 *
15730 		 * If the div in the node below gets removed:
15731 		 *  text<div>text</div>text
15732 		 *
15733 		 * Output becomes:
15734 		 *  text<div><br />text<br /></div>text
15735 		 *
15736 		 * So when the div is removed the result is:
15737 		 *  text<br />text<br />text
15738 		 *
15739 		 * @private
15740 		 * @param {Node} node Node to remove + apply BR/P elements to.
15741 		 * @param {Object} format Format rule.
15742 		 * @return {Node} Input node.
15743 		 */
15744 		function removeNode(node, format) {
15745 			var parentNode = node.parentNode, rootBlockElm;
15746 
15747 			function find(node, next, inc) {
15748 				node = getNonWhiteSpaceSibling(node, next, inc);
15749 
15750 				return !node || (node.nodeName == 'BR' || isBlock(node));
15751 			}
15752 
15753 			if (format.block) {
15754 				if (!forcedRootBlock) {
15755 					// Append BR elements if needed before we remove the block
15756 					if (isBlock(node) && !isBlock(parentNode)) {
15757 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
15758 							node.insertBefore(dom.create('br'), node.firstChild);
15759 						}
15760 
15761 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
15762 							node.appendChild(dom.create('br'));
15763 						}
15764 					}
15765 				} else {
15766 					// Wrap the block in a forcedRootBlock if we are at the root of document
15767 					if (parentNode == dom.getRoot()) {
15768 						if (!format.list_block || !isEq(node, format.list_block)) {
15769 							each(grep(node.childNodes), function(node) {
15770 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15771 									if (!rootBlockElm) {
15772 										rootBlockElm = wrap(node, forcedRootBlock);
15773 										dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
15774 									} else {
15775 										rootBlockElm.appendChild(node);
15776 									}
15777 								} else {
15778 									rootBlockElm = 0;
15779 								}
15780 							});
15781 						}
15782 					}
15783 				}
15784 			}
15785 
15786 			// Never remove nodes that isn't the specified inline element if a selector is specified too
15787 			if (format.selector && format.inline && !isEq(format.inline, node)) {
15788 				return;
15789 			}
15790 
15791 			dom.remove(node, 1);
15792 		}
15793 
15794 		/**
15795 		 * Returns the next/previous non whitespace node.
15796 		 *
15797 		 * @private
15798 		 * @param {Node} node Node to start at.
15799 		 * @param {boolean} next (Optional) Include next or previous node defaults to previous.
15800 		 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
15801 		 * @return {Node} Next or previous node or undefined if it wasn't found.
15802 		 */
15803 		function getNonWhiteSpaceSibling(node, next, inc) {
15804 			if (node) {
15805 				next = next ? 'nextSibling' : 'previousSibling';
15806 
15807 				for (node = inc ? node : node[next]; node; node = node[next]) {
15808 					if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
15809 						return node;
15810 					}
15811 				}
15812 			}
15813 		}
15814 
15815 		/**
15816 		 * Merges the next/previous sibling element if they match.
15817 		 *
15818 		 * @private
15819 		 * @param {Node} prev Previous node to compare/merge.
15820 		 * @param {Node} next Next node to compare/merge.
15821 		 * @return {Node} Next node if we didn't merge and prev node if we did.
15822 		 */
15823 		function mergeSiblings(prev, next) {
15824 			var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
15825 
15826 			function findElementSibling(node, sibling_name) {
15827 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
15828 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
15829 						return node;
15830 					}
15831 
15832 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
15833 						return sibling;
15834 					}
15835 				}
15836 
15837 				return node;
15838 			}
15839 
15840 			// Check if next/prev exists and that they are elements
15841 			if (prev && next) {
15842 				// If previous sibling is empty then jump over it
15843 				prev = findElementSibling(prev, 'previousSibling');
15844 				next = findElementSibling(next, 'nextSibling');
15845 
15846 				// Compare next and previous nodes
15847 				if (elementUtils.compare(prev, next)) {
15848 					// Append nodes between
15849 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
15850 						tmpSibling = sibling;
15851 						sibling = sibling.nextSibling;
15852 						prev.appendChild(tmpSibling);
15853 					}
15854 
15855 					// Remove next node
15856 					dom.remove(next);
15857 
15858 					// Move children into prev node
15859 					each(grep(next.childNodes), function(node) {
15860 						prev.appendChild(node);
15861 					});
15862 
15863 					return prev;
15864 				}
15865 			}
15866 
15867 			return next;
15868 		}
15869 
15870 		function getContainer(rng, start) {
15871 			var container, offset, lastIdx;
15872 
15873 			container = rng[start ? 'startContainer' : 'endContainer'];
15874 			offset = rng[start ? 'startOffset' : 'endOffset'];
15875 
15876 			if (container.nodeType == 1) {
15877 				lastIdx = container.childNodes.length - 1;
15878 
15879 				if (!start && offset) {
15880 					offset--;
15881 				}
15882 
15883 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15884 			}
15885 
15886 			// If start text node is excluded then walk to the next node
15887 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
15888 				container = new TreeWalker(container, ed.getBody()).next() || container;
15889 			}
15890 
15891 			// If end text node is excluded then walk to the previous node
15892 			if (container.nodeType === 3 && !start && offset === 0) {
15893 				container = new TreeWalker(container, ed.getBody()).prev() || container;
15894 			}
15895 
15896 			return container;
15897 		}
15898 
15899 		function performCaretAction(type, name, vars, similar) {
15900 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
15901 
15902 			// Creates a caret container bogus element
15903 			function createCaretContainer(fill) {
15904 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
15905 
15906 				if (fill) {
15907 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
15908 				}
15909 
15910 				return caretContainer;
15911 			}
15912 
15913 			function isCaretContainerEmpty(node, nodes) {
15914 				while (node) {
15915 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
15916 						return false;
15917 					}
15918 
15919 					// Collect nodes
15920 					if (nodes && node.nodeType === 1) {
15921 						nodes.push(node);
15922 					}
15923 
15924 					node = node.firstChild;
15925 				}
15926 
15927 				return true;
15928 			}
15929 
15930 			// Returns any parent caret container element
15931 			function getParentCaretContainer(node) {
15932 				while (node) {
15933 					if (node.id === caretContainerId) {
15934 						return node;
15935 					}
15936 
15937 					node = node.parentNode;
15938 				}
15939 			}
15940 
15941 			// Finds the first text node in the specified node
15942 			function findFirstTextNode(node) {
15943 				var walker;
15944 
15945 				if (node) {
15946 					walker = new TreeWalker(node, node);
15947 
15948 					for (node = walker.current(); node; node = walker.next()) {
15949 						if (node.nodeType === 3) {
15950 							return node;
15951 						}
15952 					}
15953 				}
15954 			}
15955 
15956 			// Removes the caret container for the specified node or all on the current document
15957 			function removeCaretContainer(node, move_caret) {
15958 				var child, rng;
15959 
15960 				if (!node) {
15961 					node = getParentCaretContainer(selection.getStart());
15962 
15963 					if (!node) {
15964 						while ((node = dom.get(caretContainerId))) {
15965 							removeCaretContainer(node, false);
15966 						}
15967 					}
15968 				} else {
15969 					rng = selection.getRng(true);
15970 
15971 					if (isCaretContainerEmpty(node)) {
15972 						if (move_caret !== false) {
15973 							rng.setStartBefore(node);
15974 							rng.setEndBefore(node);
15975 						}
15976 
15977 						dom.remove(node);
15978 					} else {
15979 						child = findFirstTextNode(node);
15980 
15981 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
15982 							child.deleteData(0, 1);
15983 
15984 							// Fix for bug #6976
15985 							if (rng.startContainer == child && rng.startOffset > 0) {
15986 								rng.setStart(child, rng.startOffset - 1);
15987 							}
15988 
15989 							if (rng.endContainer == child && rng.endOffset > 0) {
15990 								rng.setEnd(child, rng.endOffset - 1);
15991 							}
15992 						}
15993 
15994 						dom.remove(node, 1);
15995 					}
15996 
15997 					selection.setRng(rng);
15998 				}
15999 			}
16000 
16001 			// Applies formatting to the caret postion
16002 			function applyCaretFormat() {
16003 				var rng, caretContainer, textNode, offset, bookmark, container, text;
16004 
16005 				rng = selection.getRng(true);
16006 				offset = rng.startOffset;
16007 				container = rng.startContainer;
16008 				text = container.nodeValue;
16009 
16010 				caretContainer = getParentCaretContainer(selection.getStart());
16011 				if (caretContainer) {
16012 					textNode = findFirstTextNode(caretContainer);
16013 				}
16014 
16015 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
16016 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
16017 					// Get bookmark of caret position
16018 					bookmark = selection.getBookmark();
16019 
16020 					// Collapse bookmark range (WebKit)
16021 					rng.collapse(true);
16022 
16023 					// Expand the range to the closest word and split it at those points
16024 					rng = expandRng(rng, get(name));
16025 					rng = rangeUtils.split(rng);
16026 
16027 					// Apply the format to the range
16028 					apply(name, vars, rng);
16029 
16030 					// Move selection back to caret position
16031 					selection.moveToBookmark(bookmark);
16032 				} else {
16033 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
16034 						caretContainer = createCaretContainer(true);
16035 						textNode = caretContainer.firstChild;
16036 
16037 						rng.insertNode(caretContainer);
16038 						offset = 1;
16039 
16040 						apply(name, vars, caretContainer);
16041 					} else {
16042 						apply(name, vars, caretContainer);
16043 					}
16044 
16045 					// Move selection to text node
16046 					selection.setCursorLocation(textNode, offset);
16047 				}
16048 			}
16049 
16050 			function removeCaretFormat() {
16051 				var rng = selection.getRng(true), container, offset, bookmark,
16052 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
16053 
16054 				container = rng.startContainer;
16055 				offset = rng.startOffset;
16056 				node = container;
16057 
16058 				if (container.nodeType == 3) {
16059 					if (offset != container.nodeValue.length) {
16060 						hasContentAfter = true;
16061 					}
16062 
16063 					node = node.parentNode;
16064 				}
16065 
16066 				while (node) {
16067 					if (matchNode(node, name, vars, similar)) {
16068 						formatNode = node;
16069 						break;
16070 					}
16071 
16072 					if (node.nextSibling) {
16073 						hasContentAfter = true;
16074 					}
16075 
16076 					parents.push(node);
16077 					node = node.parentNode;
16078 				}
16079 
16080 				// Node doesn't have the specified format
16081 				if (!formatNode) {
16082 					return;
16083 				}
16084 
16085 				// Is there contents after the caret then remove the format on the element
16086 				if (hasContentAfter) {
16087 					// Get bookmark of caret position
16088 					bookmark = selection.getBookmark();
16089 
16090 					// Collapse bookmark range (WebKit)
16091 					rng.collapse(true);
16092 
16093 					// Expand the range to the closest word and split it at those points
16094 					rng = expandRng(rng, get(name), true);
16095 					rng = rangeUtils.split(rng);
16096 
16097 					// Remove the format from the range
16098 					remove(name, vars, rng);
16099 
16100 					// Move selection back to caret position
16101 					selection.moveToBookmark(bookmark);
16102 				} else {
16103 					caretContainer = createCaretContainer();
16104 
16105 					node = caretContainer;
16106 					for (i = parents.length - 1; i >= 0; i--) {
16107 						node.appendChild(dom.clone(parents[i], false));
16108 						node = node.firstChild;
16109 					}
16110 
16111 					// Insert invisible character into inner most format element
16112 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
16113 					node = node.firstChild;
16114 
16115 					var block = dom.getParent(formatNode, isTextBlock);
16116 
16117 					if (block && dom.isEmpty(block)) {
16118 						// Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
16119 						formatNode.parentNode.replaceChild(caretContainer, formatNode);
16120 					} else {
16121 						// Insert caret container after the formated node
16122 						dom.insertAfter(caretContainer, formatNode);
16123 					}
16124 
16125 					// Move selection to text node
16126 					selection.setCursorLocation(node, 1);
16127 
16128 					// If the formatNode is empty, we can remove it safely.
16129 					if (dom.isEmpty(formatNode)) {
16130 						dom.remove(formatNode);
16131 					}
16132 				}
16133 			}
16134 
16135 			// Checks if the parent caret container node isn't empty if that is the case it
16136 			// will remove the bogus state on all children that isn't empty
16137 			function unmarkBogusCaretParents() {
16138 				var caretContainer;
16139 
16140 				caretContainer = getParentCaretContainer(selection.getStart());
16141 				if (caretContainer && !dom.isEmpty(caretContainer)) {
16142 					walk(caretContainer, function(node) {
16143 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
16144 							dom.setAttrib(node, 'data-mce-bogus', null);
16145 						}
16146 					}, 'childNodes');
16147 				}
16148 			}
16149 
16150 			// Only bind the caret events once
16151 			if (!ed._hasCaretEvents) {
16152 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16153 				markCaretContainersBogus = function() {
16154 					var nodes = [], i;
16155 
16156 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16157 						// Mark children
16158 						i = nodes.length;
16159 						while (i--) {
16160 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16161 						}
16162 					}
16163 				};
16164 
16165 				disableCaretContainer = function(e) {
16166 					var keyCode = e.keyCode;
16167 
16168 					removeCaretContainer();
16169 
16170 					// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16171 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16172 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
16173 					}
16174 
16175 					unmarkBogusCaretParents();
16176 				};
16177 
16178 				// Remove bogus state if they got filled by contents using editor.selection.setContent
16179 				ed.on('SetContent', function(e) {
16180 					if (e.selection) {
16181 						unmarkBogusCaretParents();
16182 					}
16183 				});
16184 				ed._hasCaretEvents = true;
16185 			}
16186 
16187 			// Do apply or remove caret format
16188 			if (type == "apply") {
16189 				applyCaretFormat();
16190 			} else {
16191 				removeCaretFormat();
16192 			}
16193 		}
16194 
16195 		/**
16196 		 * Moves the start to the first suitable text node.
16197 		 */
16198 		function moveStart(rng) {
16199 			var container = rng.startContainer,
16200 					offset = rng.startOffset, isAtEndOfText,
16201 					walker, node, nodes, tmpNode;
16202 
16203 			// Convert text node into index if possible
16204 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
16205 				// Get the parent container location and walk from there
16206 				offset = nodeIndex(container);
16207 				container = container.parentNode;
16208 				isAtEndOfText = true;
16209 			}
16210 
16211 			// Move startContainer/startOffset in to a suitable node
16212 			if (container.nodeType == 1) {
16213 				nodes = container.childNodes;
16214 				container = nodes[Math.min(offset, nodes.length - 1)];
16215 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
16216 
16217 				// If offset is at end of the parent node walk to the next one
16218 				if (offset > nodes.length - 1 || isAtEndOfText) {
16219 					walker.next();
16220 				}
16221 
16222 				for (node = walker.current(); node; node = walker.next()) {
16223 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16224 						// IE has a "neat" feature where it moves the start node into the closest element
16225 						// we can avoid this by inserting an element before it and then remove it after we set the selection
16226 						tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
16227 						node.parentNode.insertBefore(tmpNode, node);
16228 
16229 						// Set selection and remove tmpNode
16230 						rng.setStart(node, 0);
16231 						selection.setRng(rng);
16232 						dom.remove(tmpNode);
16233 
16234 						return;
16235 					}
16236 				}
16237 			}
16238 		}
16239 	};
16240 });
16241 
16242 // Included from: js/tinymce/classes/UndoManager.js
16243 
16244 /**
16245  * UndoManager.js
16246  *
16247  * Copyright, Moxiecode Systems AB
16248  * Released under LGPL License.
16249  *
16250  * License: http://www.tinymce.com/license
16251  * Contributing: http://www.tinymce.com/contributing
16252  */
16253 
16254 /**
16255  * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
16256  *
16257  * @class tinymce.UndoManager
16258  */
16259 define("tinymce/UndoManager", [
16260 	"tinymce/Env",
16261 	"tinymce/util/Tools",
16262 	"tinymce/html/SaxParser"
16263 ], function(Env, Tools, SaxParser) {
16264 	var trim = Tools.trim, trimContentRegExp;
16265 
16266 	trimContentRegExp = new RegExp([
16267 		'<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
16268 		'\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
16269 	].join('|'), 'gi');
16270 
16271 	return function(editor) {
16272 		var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
16273 
16274 		/**
16275 		 * Returns a trimmed version of the editor contents to be used for the undo level. This
16276 		 * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
16277 		 * remove the data-mce-selected attributes used for selection of objects and caret containers.
16278 		 * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
16279 		 * be removed by the serialization logic when you save.
16280 		 *
16281 		 * @private
16282 		 * @return {String} HTML contents of the editor excluding some internal bogus elements.
16283 		 */
16284 		function getContent() {
16285 			var content = editor.getContent({format: 'raw', no_events: 1});
16286 			var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
16287 			var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
16288 
16289 			content = content.replace(trimContentRegExp, '');
16290 			shortEndedElements = schema.getShortEndedElements();
16291 
16292 			// Remove all bogus elements marked with "all"
16293 			while ((matches = bogusAllRegExp.exec(content))) {
16294 				index = bogusAllRegExp.lastIndex;
16295 				matchLength = matches[0].length;
16296 
16297 				if (shortEndedElements[matches[1]]) {
16298 					endTagIndex = index;
16299 				} else {
16300 					endTagIndex = SaxParser.findEndTag(schema, content, index);
16301 				}
16302 
16303 				content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
16304 				bogusAllRegExp.lastIndex = index - matchLength;
16305 			}
16306 
16307 			return trim(content);
16308 		}
16309 
16310 		function addNonTypingUndoLevel(e) {
16311 			self.typing = false;
16312 			self.add({}, e);
16313 		}
16314 
16315 		// Add initial undo level when the editor is initialized
16316 		editor.on('init', function() {
16317 			self.add();
16318 		});
16319 
16320 		// Get position before an execCommand is processed
16321 		editor.on('BeforeExecCommand', function(e) {
16322 			var cmd = e.command;
16323 
16324 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
16325 				self.beforeChange();
16326 			}
16327 		});
16328 
16329 		// Add undo level after an execCommand call was made
16330 		editor.on('ExecCommand', function(e) {
16331 			var cmd = e.command;
16332 
16333 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
16334 				addNonTypingUndoLevel(e);
16335 			}
16336 		});
16337 
16338 		editor.on('ObjectResizeStart', function() {
16339 			self.beforeChange();
16340 		});
16341 
16342 		editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
16343 		editor.on('DragEnd', addNonTypingUndoLevel);
16344 
16345 		editor.on('KeyUp', function(e) {
16346 			var keyCode = e.keyCode;
16347 
16348 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
16349 				addNonTypingUndoLevel();
16350 				editor.nodeChanged();
16351 			}
16352 
16353 			if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
16354 				editor.nodeChanged();
16355 			}
16356 
16357 			// Fire a TypingUndo event on the first character entered
16358 			if (isFirstTypedCharacter && self.typing) {
16359 				// Make the it dirty if the content was changed after typing the first character
16360 				if (!editor.isDirty()) {
16361 					editor.isNotDirty = !data[0] || getContent() == data[0].content;
16362 
16363 					// Fire initial change event
16364 					if (!editor.isNotDirty) {
16365 						editor.fire('change', {level: data[0], lastLevel: null});
16366 					}
16367 				}
16368 
16369 				editor.fire('TypingUndo');
16370 				isFirstTypedCharacter = false;
16371 				editor.nodeChanged();
16372 			}
16373 		});
16374 
16375 		editor.on('KeyDown', function(e) {
16376 			var keyCode = e.keyCode;
16377 
16378 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
16379 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
16380 				if (self.typing) {
16381 					addNonTypingUndoLevel(e);
16382 				}
16383 
16384 				return;
16385 			}
16386 
16387 			// If key isn't shift,ctrl,alt,capslock,metakey
16388 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
16389 				self.beforeChange();
16390 				self.typing = true;
16391 				self.add({}, e);
16392 				isFirstTypedCharacter = true;
16393 			}
16394 		});
16395 
16396 		editor.on('MouseDown', function(e) {
16397 			if (self.typing) {
16398 				addNonTypingUndoLevel(e);
16399 			}
16400 		});
16401 
16402 		// Add keyboard shortcuts for undo/redo keys
16403 		editor.addShortcut('ctrl+z', '', 'Undo');
16404 		editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');
16405 
16406 		editor.on('AddUndo Undo Redo ClearUndos', function(e) {
16407 			if (!e.isDefaultPrevented()) {
16408 				editor.nodeChanged();
16409 			}
16410 		});
16411 
16412 		self = {
16413 			// Explose for debugging reasons
16414 			data: data,
16415 
16416 			/**
16417 			 * State if the user is currently typing or not. This will add a typing operation into one undo
16418 			 * level instead of one new level for each keystroke.
16419 			 *
16420 			 * @field {Boolean} typing
16421 			 */
16422 			typing: false,
16423 
16424 			/**
16425 			 * Stores away a bookmark to be used when performing an undo action so that the selection is before
16426 			 * the change has been made.
16427 			 *
16428 			 * @method beforeChange
16429 			 */
16430 			beforeChange: function() {
16431 				if (!locks) {
16432 					beforeBookmark = editor.selection.getBookmark(2, true);
16433 				}
16434 			},
16435 
16436 			/**
16437 			 * Adds a new undo level/snapshot to the undo list.
16438 			 *
16439 			 * @method add
16440 			 * @param {Object} level Optional undo level object to add.
16441 			 * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
16442 			 * @return {Object} Undo level that got added or null it a level wasn't needed.
16443 			 */
16444 			add: function(level, event) {
16445 				var i, settings = editor.settings, lastLevel;
16446 
16447 				level = level || {};
16448 				level.content = getContent();
16449 
16450 				if (locks || editor.removed) {
16451 					return null;
16452 				}
16453 
16454 				lastLevel = data[index];
16455 				if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
16456 					return null;
16457 				}
16458 
16459 				// Add undo level if needed
16460 				if (lastLevel && lastLevel.content == level.content) {
16461 					return null;
16462 				}
16463 
16464 				// Set before bookmark on previous level
16465 				if (data[index]) {
16466 					data[index].beforeBookmark = beforeBookmark;
16467 				}
16468 
16469 				// Time to compress
16470 				if (settings.custom_undo_redo_levels) {
16471 					if (data.length > settings.custom_undo_redo_levels) {
16472 						for (i = 0; i < data.length - 1; i++) {
16473 							data[i] = data[i + 1];
16474 						}
16475 
16476 						data.length--;
16477 						index = data.length;
16478 					}
16479 				}
16480 
16481 				// Get a non intrusive normalized bookmark
16482 				level.bookmark = editor.selection.getBookmark(2, true);
16483 
16484 				// Crop array if needed
16485 				if (index < data.length - 1) {
16486 					data.length = index + 1;
16487 				}
16488 
16489 				data.push(level);
16490 				index = data.length - 1;
16491 
16492 				var args = {level: level, lastLevel: lastLevel, originalEvent: event};
16493 
16494 				editor.fire('AddUndo', args);
16495 
16496 				if (index > 0) {
16497 					editor.isNotDirty = false;
16498 					editor.fire('change', args);
16499 				}
16500 
16501 				return level;
16502 			},
16503 
16504 			/**
16505 			 * Undoes the last action.
16506 			 *
16507 			 * @method undo
16508 			 * @return {Object} Undo level or null if no undo was performed.
16509 			 */
16510 			undo: function() {
16511 				var level;
16512 
16513 				if (self.typing) {
16514 					self.add();
16515 					self.typing = false;
16516 				}
16517 
16518 				if (index > 0) {
16519 					level = data[--index];
16520 
16521 					// Undo to first index then set dirty state to false
16522 					if (index === 0) {
16523 						editor.isNotDirty = true;
16524 					}
16525 
16526 					editor.setContent(level.content, {format: 'raw'});
16527 					editor.selection.moveToBookmark(level.beforeBookmark);
16528 
16529 					editor.fire('undo', {level: level});
16530 				}
16531 
16532 				return level;
16533 			},
16534 
16535 			/**
16536 			 * Redoes the last action.
16537 			 *
16538 			 * @method redo
16539 			 * @return {Object} Redo level or null if no redo was performed.
16540 			 */
16541 			redo: function() {
16542 				var level;
16543 
16544 				if (index < data.length - 1) {
16545 					level = data[++index];
16546 
16547 					editor.setContent(level.content, {format: 'raw'});
16548 					editor.selection.moveToBookmark(level.bookmark);
16549 
16550 					editor.fire('redo', {level: level});
16551 				}
16552 
16553 				return level;
16554 			},
16555 
16556 			/**
16557 			 * Removes all undo levels.
16558 			 *
16559 			 * @method clear
16560 			 */
16561 			clear: function() {
16562 				data = [];
16563 				index = 0;
16564 				self.typing = false;
16565 				editor.fire('ClearUndos');
16566 			},
16567 
16568 			/**
16569 			 * Returns true/false if the undo manager has any undo levels.
16570 			 *
16571 			 * @method hasUndo
16572 			 * @return {Boolean} true/false if the undo manager has any undo levels.
16573 			 */
16574 			hasUndo: function() {
16575 				// Has undo levels or typing and content isn't the same as the initial level
16576 				return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
16577 			},
16578 
16579 			/**
16580 			 * Returns true/false if the undo manager has any redo levels.
16581 			 *
16582 			 * @method hasRedo
16583 			 * @return {Boolean} true/false if the undo manager has any redo levels.
16584 			 */
16585 			hasRedo: function() {
16586 				return index < data.length - 1 && !this.typing;
16587 			},
16588 
16589 			/**
16590 			 * Executes the specified function in an undo transation. The selection
16591 			 * before the modification will be stored to the undo stack and if the DOM changes
16592 			 * it will add a new undo level. Any methods within the transation that adds undo levels will
16593 			 * be ignored. So a transation can include calls to execCommand or editor.insertContent.
16594 			 *
16595 			 * @method transact
16596 			 * @param {function} callback Function to execute dom manipulation logic in.
16597 			 */
16598 			transact: function(callback) {
16599 				self.beforeChange();
16600 
16601 				try {
16602 					locks++;
16603 					callback();
16604 				} finally {
16605 					locks--;
16606 				}
16607 
16608 				self.add();
16609 			}
16610 		};
16611 
16612 		return self;
16613 	};
16614 });
16615 
16616 // Included from: js/tinymce/classes/EnterKey.js
16617 
16618 /**
16619  * EnterKey.js
16620  *
16621  * Copyright, Moxiecode Systems AB
16622  * Released under LGPL License.
16623  *
16624  * License: http://www.tinymce.com/license
16625  * Contributing: http://www.tinymce.com/contributing
16626  */
16627 
16628 /**
16629  * Contains logic for handling the enter key to split/generate block elements.
16630  */
16631 define("tinymce/EnterKey", [
16632 	"tinymce/dom/TreeWalker",
16633 	"tinymce/dom/RangeUtils",
16634 	"tinymce/Env"
16635 ], function(TreeWalker, RangeUtils, Env) {
16636 	var isIE = Env.ie && Env.ie < 11;
16637 
16638 	return function(editor) {
16639 		var dom = editor.dom, selection = editor.selection, settings = editor.settings;
16640 		var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
16641 
16642 		function handleEnterKey(evt) {
16643 			var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
16644 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
16645 
16646 			// Returns true if the block can be split into two blocks or not
16647 			function canSplitBlock(node) {
16648 				return node &&
16649 					dom.isBlock(node) &&
16650 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
16651 					!/^(fixed|absolute)/i.test(node.style.position) &&
16652 					dom.getContentEditable(node) !== "true";
16653 			}
16654 
16655 			// Renders empty block on IE
16656 			function renderBlockOnIE(block) {
16657 				var oldRng;
16658 
16659 				if (dom.isBlock(block)) {
16660 					oldRng = selection.getRng();
16661 					block.appendChild(dom.create('span', null, '\u00a0'));
16662 					selection.select(block);
16663 					block.lastChild.outerHTML = '';
16664 					selection.setRng(oldRng);
16665 				}
16666 			}
16667 
16668 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
16669 			function trimInlineElementsOnLeftSideOfBlock(block) {
16670 				var node = block, firstChilds = [], i;
16671 
16672 				if (!node) {
16673 					return;
16674 				}
16675 
16676 				// Find inner most first child ex: <p><i><b>*</b></i></p>
16677 				while ((node = node.firstChild)) {
16678 					if (dom.isBlock(node)) {
16679 						return;
16680 					}
16681 
16682 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16683 						firstChilds.push(node);
16684 					}
16685 				}
16686 
16687 				i = firstChilds.length;
16688 				while (i--) {
16689 					node = firstChilds[i];
16690 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
16691 						dom.remove(node);
16692 					} else {
16693 						// Remove <a> </a> see #5381
16694 						if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
16695 							dom.remove(node);
16696 						}
16697 					}
16698 				}
16699 			}
16700 
16701 			// Moves the caret to a suitable position within the root for example in the first non
16702 			// pure whitespace text node or before an image
16703 			function moveToCaretPosition(root) {
16704 				var walker, node, rng, lastNode = root, tempElm;
16705 
16706 				function firstNonWhiteSpaceNodeSibling(node) {
16707 					while (node) {
16708 						if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
16709 							return node;
16710 						}
16711 
16712 						node = node.nextSibling;
16713 					}
16714 				}
16715 
16716 				if (!root) {
16717 					return;
16718 				}
16719 
16720 				// Old IE versions doesn't properly render blocks with br elements in them
16721 				// For example <p><br></p> wont be rendered correctly in a contentEditable area
16722 				// until you remove the br producing <p></p>
16723 				if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
16724 					if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
16725 						dom.remove(parentBlock.firstChild);
16726 					}
16727 				}
16728 
16729 				if (/^(LI|DT|DD)$/.test(root.nodeName)) {
16730 					var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
16731 
16732 					if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
16733 						root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
16734 					}
16735 				}
16736 
16737 				rng = dom.createRng();
16738 
16739 				// Normalize whitespace to remove empty text nodes. Fix for: #6904
16740 				// Gecko will be able to place the caret in empty text nodes but it won't render propery
16741 				// Older IE versions will sometimes crash so for now ignore all IE versions
16742 				if (!Env.ie) {
16743 					root.normalize();
16744 				}
16745 
16746 				if (root.hasChildNodes()) {
16747 					walker = new TreeWalker(root, root);
16748 
16749 					while ((node = walker.current())) {
16750 						if (node.nodeType == 3) {
16751 							rng.setStart(node, 0);
16752 							rng.setEnd(node, 0);
16753 							break;
16754 						}
16755 
16756 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16757 							rng.setStartBefore(node);
16758 							rng.setEndBefore(node);
16759 							break;
16760 						}
16761 
16762 						lastNode = node;
16763 						node = walker.next();
16764 					}
16765 
16766 					if (!node) {
16767 						rng.setStart(lastNode, 0);
16768 						rng.setEnd(lastNode, 0);
16769 					}
16770 				} else {
16771 					if (root.nodeName == 'BR') {
16772 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
16773 							// Trick on older IE versions to render the caret before the BR between two lists
16774 							if (!documentMode || documentMode < 9) {
16775 								tempElm = dom.create('br');
16776 								root.parentNode.insertBefore(tempElm, root);
16777 							}
16778 
16779 							rng.setStartBefore(root);
16780 							rng.setEndBefore(root);
16781 						} else {
16782 							rng.setStartAfter(root);
16783 							rng.setEndAfter(root);
16784 						}
16785 					} else {
16786 						rng.setStart(root, 0);
16787 						rng.setEnd(root, 0);
16788 					}
16789 				}
16790 
16791 				selection.setRng(rng);
16792 
16793 				// Remove tempElm created for old IE:s
16794 				dom.remove(tempElm);
16795 				selection.scrollIntoView(root);
16796 			}
16797 
16798 			function setForcedBlockAttrs(node) {
16799 				var forcedRootBlockName = settings.forced_root_block;
16800 
16801 				if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
16802 					dom.setAttribs(node, settings.forced_root_block_attrs);
16803 				}
16804 			}
16805 
16806 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
16807 			// This function will also copy any text formatting from the parent block and add it to the new one
16808 			function createNewBlock(name) {
16809 				var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
16810 
16811 				if (name || parentBlockName == "TABLE") {
16812 					block = dom.create(name || newBlockName);
16813 					setForcedBlockAttrs(block);
16814 				} else {
16815 					block = parentBlock.cloneNode(false);
16816 				}
16817 
16818 				caretNode = block;
16819 
16820 				// Clone any parent styles
16821 				if (settings.keep_styles !== false) {
16822 					do {
16823 						if (textInlineElements[node.nodeName]) {
16824 							// Never clone a caret containers
16825 							if (node.id == '_mce_caret') {
16826 								continue;
16827 							}
16828 
16829 							clonedNode = node.cloneNode(false);
16830 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
16831 
16832 							if (block.hasChildNodes()) {
16833 								clonedNode.appendChild(block.firstChild);
16834 								block.appendChild(clonedNode);
16835 							} else {
16836 								caretNode = clonedNode;
16837 								block.appendChild(clonedNode);
16838 							}
16839 						}
16840 					} while ((node = node.parentNode));
16841 				}
16842 
16843 				// BR is needed in empty blocks on non IE browsers
16844 				if (!isIE) {
16845 					caretNode.innerHTML = '<br data-mce-bogus="1">';
16846 				}
16847 
16848 				return block;
16849 			}
16850 
16851 			// Returns true/false if the caret is at the start/end of the parent block element
16852 			function isCaretAtStartOrEndOfBlock(start) {
16853 				var walker, node, name;
16854 
16855 				// Caret is in the middle of a text node like "a|b"
16856 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
16857 					return false;
16858 				}
16859 
16860 				// If after the last element in block node edge case for #5091
16861 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
16862 					return true;
16863 				}
16864 
16865 				// If the caret if before the first element in parentBlock
16866 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
16867 					return true;
16868 				}
16869 
16870 				// Caret can be before/after a table
16871 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
16872 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
16873 				}
16874 
16875 				// Walk the DOM and look for text nodes or non empty elements
16876 				walker = new TreeWalker(container, parentBlock);
16877 
16878 				// If caret is in beginning or end of a text block then jump to the next/previous node
16879 				if (container.nodeType == 3) {
16880 					if (start && offset === 0) {
16881 						walker.prev();
16882 					} else if (!start && offset == container.nodeValue.length) {
16883 						walker.next();
16884 					}
16885 				}
16886 
16887 				while ((node = walker.current())) {
16888 					if (node.nodeType === 1) {
16889 						// Ignore bogus elements
16890 						if (!node.getAttribute('data-mce-bogus')) {
16891 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
16892 							name = node.nodeName.toLowerCase();
16893 							if (nonEmptyElementsMap[name] && name !== 'br') {
16894 								return false;
16895 							}
16896 						}
16897 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
16898 						return false;
16899 					}
16900 
16901 					if (start) {
16902 						walker.prev();
16903 					} else {
16904 						walker.next();
16905 					}
16906 				}
16907 
16908 				return true;
16909 			}
16910 
16911 			// Wraps any text nodes or inline elements in the specified forced root block name
16912 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
16913 				var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
16914 
16915 				// Not in a block element or in a table cell or caption
16916 				parentBlock = dom.getParent(container, dom.isBlock);
16917 				rootBlockName = editor.getBody().nodeName.toLowerCase();
16918 				if (!parentBlock || !canSplitBlock(parentBlock)) {
16919 					parentBlock = parentBlock || editableRoot;
16920 
16921 					if (!parentBlock.hasChildNodes()) {
16922 						newBlock = dom.create(blockName);
16923 						setForcedBlockAttrs(newBlock);
16924 						parentBlock.appendChild(newBlock);
16925 						rng.setStart(newBlock, 0);
16926 						rng.setEnd(newBlock, 0);
16927 						return newBlock;
16928 					}
16929 
16930 					// Find parent that is the first child of parentBlock
16931 					node = container;
16932 					while (node.parentNode != parentBlock) {
16933 						node = node.parentNode;
16934 					}
16935 
16936 					// Loop left to find start node start wrapping at
16937 					while (node && !dom.isBlock(node)) {
16938 						startNode = node;
16939 						node = node.previousSibling;
16940 					}
16941 
16942 					if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
16943 						newBlock = dom.create(blockName);
16944 						setForcedBlockAttrs(newBlock);
16945 						startNode.parentNode.insertBefore(newBlock, startNode);
16946 
16947 						// Start wrapping until we hit a block
16948 						node = startNode;
16949 						while (node && !dom.isBlock(node)) {
16950 							next = node.nextSibling;
16951 							newBlock.appendChild(node);
16952 							node = next;
16953 						}
16954 
16955 						// Restore range to it's past location
16956 						rng.setStart(container, offset);
16957 						rng.setEnd(container, offset);
16958 					}
16959 				}
16960 
16961 				return container;
16962 			}
16963 
16964 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
16965 			function handleEmptyListItem() {
16966 				function isFirstOrLastLi(first) {
16967 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
16968 
16969 					// Find first/last element since there might be whitespace there
16970 					while (node) {
16971 						if (node.nodeType == 1) {
16972 							break;
16973 						}
16974 
16975 						node = node[first ? 'nextSibling' : 'previousSibling'];
16976 					}
16977 
16978 					return node === parentBlock;
16979 				}
16980 
16981 				function getContainerBlock() {
16982 					var containerBlockParent = containerBlock.parentNode;
16983 
16984 					if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
16985 						return containerBlockParent;
16986 					}
16987 
16988 					return containerBlock;
16989 				}
16990 
16991 				// Check if we are in an nested list
16992 				var containerBlockParentName = containerBlock.parentNode.nodeName;
16993 				if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
16994 					newBlockName = 'LI';
16995 				}
16996 
16997 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
16998 
16999 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
17000 					if (containerBlockParentName == 'LI') {
17001 						// Nested list is inside a LI
17002 						dom.insertAfter(newBlock, getContainerBlock());
17003 					} else {
17004 						// Is first and last list item then replace the OL/UL with a text block
17005 						dom.replace(newBlock, containerBlock);
17006 					}
17007 				} else if (isFirstOrLastLi(true)) {
17008 					if (containerBlockParentName == 'LI') {
17009 						// List nested in an LI then move the list to a new sibling LI
17010 						dom.insertAfter(newBlock, getContainerBlock());
17011 						newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
17012 						newBlock.appendChild(containerBlock);
17013 					} else {
17014 						// First LI in list then remove LI and add text block before list
17015 						containerBlock.parentNode.insertBefore(newBlock, containerBlock);
17016 					}
17017 				} else if (isFirstOrLastLi()) {
17018 					// Last LI in list then remove LI and add text block after list
17019 					dom.insertAfter(newBlock, getContainerBlock());
17020 					renderBlockOnIE(newBlock);
17021 				} else {
17022 					// Middle LI in list the split the list and insert a text block in the middle
17023 					// Extract after fragment and insert it after the current block
17024 					containerBlock = getContainerBlock();
17025 					tmpRng = rng.cloneRange();
17026 					tmpRng.setStartAfter(parentBlock);
17027 					tmpRng.setEndAfter(containerBlock);
17028 					fragment = tmpRng.extractContents();
17029 
17030 					if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
17031 						newBlock = fragment.firstChild;
17032 						dom.insertAfter(fragment, containerBlock);
17033 					} else {
17034 						dom.insertAfter(fragment, containerBlock);
17035 						dom.insertAfter(newBlock, containerBlock);
17036 					}
17037 				}
17038 
17039 				dom.remove(parentBlock);
17040 				moveToCaretPosition(newBlock);
17041 				undoManager.add();
17042 			}
17043 
17044 			// Inserts a BR element if the forced_root_block option is set to false or empty string
17045 			function insertBr() {
17046 				editor.execCommand("InsertLineBreak", false, evt);
17047 			}
17048 
17049 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
17050 			function trimLeadingLineBreaks(node) {
17051 				do {
17052 					if (node.nodeType === 3) {
17053 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
17054 					}
17055 
17056 					node = node.firstChild;
17057 				} while (node);
17058 			}
17059 
17060 			function getEditableRoot(node) {
17061 				var root = dom.getRoot(), parent, editableRoot;
17062 
17063 				// Get all parents until we hit a non editable parent or the root
17064 				parent = node;
17065 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
17066 					if (dom.getContentEditable(parent) === "true") {
17067 						editableRoot = parent;
17068 					}
17069 
17070 					parent = parent.parentNode;
17071 				}
17072 
17073 				return parent !== root ? editableRoot : root;
17074 			}
17075 
17076 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since
17077 			// these might be floated and then they won't expand the block
17078 			function addBrToBlockIfNeeded(block) {
17079 				var lastChild;
17080 
17081 				// IE will render the blocks correctly other browsers needs a BR
17082 				if (!isIE) {
17083 					block.normalize(); // Remove empty text nodes that got left behind by the extract
17084 
17085 					// Check if the block is empty or contains a floated last child
17086 					lastChild = block.lastChild;
17087 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
17088 						dom.add(block, 'br');
17089 					}
17090 				}
17091 			}
17092 
17093 			rng = selection.getRng(true);
17094 
17095 			// Event is blocked by some other handler for example the lists plugin
17096 			if (evt.isDefaultPrevented()) {
17097 				return;
17098 			}
17099 
17100 			// Delete any selected contents
17101 			if (!rng.collapsed) {
17102 				editor.execCommand('Delete');
17103 				return;
17104 			}
17105 
17106 			// Setup range items and newBlockName
17107 			new RangeUtils(dom).normalize(rng);
17108 			container = rng.startContainer;
17109 			offset = rng.startOffset;
17110 			newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
17111 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
17112 			documentMode = dom.doc.documentMode;
17113 			shiftKey = evt.shiftKey;
17114 
17115 			// Resolve node index
17116 			if (container.nodeType == 1 && container.hasChildNodes()) {
17117 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17118 
17119 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17120 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
17121 					offset = container.nodeValue.length;
17122 				} else {
17123 					offset = 0;
17124 				}
17125 			}
17126 
17127 			// Get editable root node normaly the body element but sometimes a div or span
17128 			editableRoot = getEditableRoot(container);
17129 
17130 			// If there is no editable root then enter is done inside a contentEditable false element
17131 			if (!editableRoot) {
17132 				return;
17133 			}
17134 
17135 			undoManager.beforeChange();
17136 
17137 			// If editable root isn't block nor the root of the editor
17138 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
17139 				if (!newBlockName || shiftKey) {
17140 					insertBr();
17141 				}
17142 
17143 				return;
17144 			}
17145 
17146 			// Wrap the current node and it's sibling in a default block if it's needed.
17147 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
17148 			// This won't happen if root blocks are disabled or the shiftKey is pressed
17149 			if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
17150 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
17151 			}
17152 
17153 			// Find parent block and setup empty block paddings
17154 			parentBlock = dom.getParent(container, dom.isBlock);
17155 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
17156 
17157 			// Setup block names
17158 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17159 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17160 
17161 			// Enter inside block contained within a LI then split or insert before/after LI
17162 			if (containerBlockName == 'LI' && !evt.ctrlKey) {
17163 				parentBlock = containerBlock;
17164 				parentBlockName = containerBlockName;
17165 			}
17166 
17167 			// Handle enter in list item
17168 			if (/^(LI|DT|DD)$/.test(parentBlockName)) {
17169 				if (!newBlockName && shiftKey) {
17170 					insertBr();
17171 					return;
17172 				}
17173 
17174 				// Handle enter inside an empty list item
17175 				if (dom.isEmpty(parentBlock)) {
17176 					handleEmptyListItem();
17177 					return;
17178 				}
17179 			}
17180 
17181 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
17182 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
17183 				if (!shiftKey) {
17184 					insertBr();
17185 					return;
17186 				}
17187 			} else {
17188 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
17189 				if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
17190 					insertBr();
17191 					return;
17192 				}
17193 			}
17194 
17195 			// If parent block is root then never insert new blocks
17196 			if (newBlockName && parentBlock === editor.getBody()) {
17197 				return;
17198 			}
17199 
17200 			// Default block name if it's not configured
17201 			newBlockName = newBlockName || 'P';
17202 
17203 			// Insert new block before/after the parent block depending on caret location
17204 			if (isCaretAtStartOrEndOfBlock()) {
17205 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
17206 				if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
17207 					newBlock = createNewBlock(newBlockName);
17208 				} else {
17209 					newBlock = createNewBlock();
17210 				}
17211 
17212 				// Split the current container block element if enter is pressed inside an empty inner block element
17213 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
17214 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
17215 					newBlock = dom.split(containerBlock, parentBlock);
17216 				} else {
17217 					dom.insertAfter(newBlock, parentBlock);
17218 				}
17219 
17220 				moveToCaretPosition(newBlock);
17221 			} else if (isCaretAtStartOrEndOfBlock(true)) {
17222 				// Insert new block before
17223 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
17224 				renderBlockOnIE(newBlock);
17225 				moveToCaretPosition(parentBlock);
17226 			} else {
17227 				// Extract after fragment and insert it after the current block
17228 				tmpRng = rng.cloneRange();
17229 				tmpRng.setEndAfter(parentBlock);
17230 				fragment = tmpRng.extractContents();
17231 				trimLeadingLineBreaks(fragment);
17232 				newBlock = fragment.firstChild;
17233 				dom.insertAfter(fragment, parentBlock);
17234 				trimInlineElementsOnLeftSideOfBlock(newBlock);
17235 				addBrToBlockIfNeeded(parentBlock);
17236 				moveToCaretPosition(newBlock);
17237 			}
17238 
17239 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
17240 
17241 			// Allow custom handling of new blocks
17242 			editor.fire('NewBlock', {newBlock: newBlock});
17243 
17244 			undoManager.add();
17245 		}
17246 
17247 		editor.on('keydown', function(evt) {
17248 			if (evt.keyCode == 13) {
17249 				if (handleEnterKey(evt) !== false) {
17250 					evt.preventDefault();
17251 				}
17252 			}
17253 		});
17254 	};
17255 });
17256 
17257 // Included from: js/tinymce/classes/ForceBlocks.js
17258 
17259 /**
17260  * ForceBlocks.js
17261  *
17262  * Copyright, Moxiecode Systems AB
17263  * Released under LGPL License.
17264  *
17265  * License: http://www.tinymce.com/license
17266  * Contributing: http://www.tinymce.com/contributing
17267  */
17268 
17269 define("tinymce/ForceBlocks", [], function() {
17270 	return function(editor) {
17271 		var settings = editor.settings, dom = editor.dom, selection = editor.selection;
17272 		var schema = editor.schema, blockElements = schema.getBlockElements();
17273 
17274 		function addRootBlocks() {
17275 			var node = selection.getStart(), rootNode = editor.getBody(), rng;
17276 			var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
17277 			var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
17278 			var tmpRng, rootNodeName, forcedRootBlock;
17279 
17280 			forcedRootBlock = settings.forced_root_block;
17281 
17282 			if (!node || node.nodeType !== 1 || !forcedRootBlock) {
17283 				return;
17284 			}
17285 
17286 			// Check if node is wrapped in block
17287 			while (node && node != rootNode) {
17288 				if (blockElements[node.nodeName]) {
17289 					return;
17290 				}
17291 
17292 				node = node.parentNode;
17293 			}
17294 
17295 			// Get current selection
17296 			rng = selection.getRng();
17297 			if (rng.setStart) {
17298 				startContainer = rng.startContainer;
17299 				startOffset = rng.startOffset;
17300 				endContainer = rng.endContainer;
17301 				endOffset = rng.endOffset;
17302 
17303 				try {
17304 					restoreSelection = editor.getDoc().activeElement === rootNode;
17305 				} catch (ex) {
17306 					// IE throws unspecified error here sometimes
17307 				}
17308 			} else {
17309 				// Force control range into text range
17310 				if (rng.item) {
17311 					node = rng.item(0);
17312 					rng = editor.getDoc().body.createTextRange();
17313 					rng.moveToElementText(node);
17314 				}
17315 
17316 				restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
17317 				tmpRng = rng.duplicate();
17318 				tmpRng.collapse(true);
17319 				startOffset = tmpRng.move('character', offset) * -1;
17320 
17321 				if (!tmpRng.collapsed) {
17322 					tmpRng = rng.duplicate();
17323 					tmpRng.collapse(false);
17324 					endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
17325 				}
17326 			}
17327 
17328 			// Wrap non block elements and text nodes
17329 			node = rootNode.firstChild;
17330 			rootNodeName = rootNode.nodeName.toLowerCase();
17331 			while (node) {
17332 				// TODO: Break this up, too complex
17333 				if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
17334 					schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
17335 					// Remove empty text nodes
17336 					if (node.nodeType === 3 && node.nodeValue.length === 0) {
17337 						tempNode = node;
17338 						node = node.nextSibling;
17339 						dom.remove(tempNode);
17340 						continue;
17341 					}
17342 
17343 					if (!rootBlockNode) {
17344 						rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
17345 						node.parentNode.insertBefore(rootBlockNode, node);
17346 						wrapped = true;
17347 					}
17348 
17349 					tempNode = node;
17350 					node = node.nextSibling;
17351 					rootBlockNode.appendChild(tempNode);
17352 				} else {
17353 					rootBlockNode = null;
17354 					node = node.nextSibling;
17355 				}
17356 			}
17357 
17358 			if (wrapped && restoreSelection) {
17359 				if (rng.setStart) {
17360 					rng.setStart(startContainer, startOffset);
17361 					rng.setEnd(endContainer, endOffset);
17362 					selection.setRng(rng);
17363 				} else {
17364 					// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
17365 					try {
17366 						rng = editor.getDoc().body.createTextRange();
17367 						rng.moveToElementText(rootNode);
17368 						rng.collapse(true);
17369 						rng.moveStart('character', startOffset);
17370 
17371 						if (endOffset > 0) {
17372 							rng.moveEnd('character', endOffset);
17373 						}
17374 
17375 						rng.select();
17376 					} catch (ex) {
17377 						// Ignore
17378 					}
17379 				}
17380 
17381 				editor.nodeChanged();
17382 			}
17383 		}
17384 
17385 		// Force root blocks
17386 		if (settings.forced_root_block) {
17387 			editor.on('NodeChange', addRootBlocks);
17388 		}
17389 	};
17390 });
17391 
17392 // Included from: js/tinymce/classes/EditorCommands.js
17393 
17394 /**
17395  * EditorCommands.js
17396  *
17397  * Copyright, Moxiecode Systems AB
17398  * Released under LGPL License.
17399  *
17400  * License: http://www.tinymce.com/license
17401  * Contributing: http://www.tinymce.com/contributing
17402  */
17403 
17404 /**
17405  * This class enables you to add custom editor commands and it contains
17406  * overrides for native browser commands to address various bugs and issues.
17407  *
17408  * @class tinymce.EditorCommands
17409  */
17410 define("tinymce/EditorCommands", [
17411 	"tinymce/html/Serializer",
17412 	"tinymce/Env",
17413 	"tinymce/util/Tools",
17414 	"tinymce/dom/ElementUtils",
17415 	"tinymce/dom/RangeUtils",
17416 	"tinymce/dom/TreeWalker"
17417 ], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
17418 	// Added for compression purposes
17419 	var each = Tools.each, extend = Tools.extend;
17420 	var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
17421 	var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
17422 	var TRUE = true, FALSE = false;
17423 
17424 	return function(editor) {
17425 		var dom = editor.dom,
17426 			selection = editor.selection,
17427 			commands = {state: {}, exec: {}, value: {}},
17428 			settings = editor.settings,
17429 			formatter = editor.formatter,
17430 			bookmark;
17431 
17432 		/**
17433 		 * Executes the specified command.
17434 		 *
17435 		 * @method execCommand
17436 		 * @param {String} command Command to execute.
17437 		 * @param {Boolean} ui Optional user interface state.
17438 		 * @param {Object} value Optional value for command.
17439 		 * @return {Boolean} true/false if the command was found or not.
17440 		 */
17441 		function execCommand(command, ui, value) {
17442 			var func;
17443 
17444 			command = command.toLowerCase();
17445 			if ((func = commands.exec[command])) {
17446 				func(command, ui, value);
17447 				return TRUE;
17448 			}
17449 
17450 			return FALSE;
17451 		}
17452 
17453 		/**
17454 		 * Queries the current state for a command for example if the current selection is "bold".
17455 		 *
17456 		 * @method queryCommandState
17457 		 * @param {String} command Command to check the state of.
17458 		 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
17459 		 */
17460 		function queryCommandState(command) {
17461 			var func;
17462 
17463 			command = command.toLowerCase();
17464 			if ((func = commands.state[command])) {
17465 				return func(command);
17466 			}
17467 
17468 			return -1;
17469 		}
17470 
17471 		/**
17472 		 * Queries the command value for example the current fontsize.
17473 		 *
17474 		 * @method queryCommandValue
17475 		 * @param {String} command Command to check the value of.
17476 		 * @return {Object} Command value of false if it's not found.
17477 		 */
17478 		function queryCommandValue(command) {
17479 			var func;
17480 
17481 			command = command.toLowerCase();
17482 			if ((func = commands.value[command])) {
17483 				return func(command);
17484 			}
17485 
17486 			return FALSE;
17487 		}
17488 
17489 		/**
17490 		 * Adds commands to the command collection.
17491 		 *
17492 		 * @method addCommands
17493 		 * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
17494 		 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
17495 		 */
17496 		function addCommands(command_list, type) {
17497 			type = type || 'exec';
17498 
17499 			each(command_list, function(callback, command) {
17500 				each(command.toLowerCase().split(','), function(command) {
17501 					commands[type][command] = callback;
17502 				});
17503 			});
17504 		}
17505 
17506 		// Expose public methods
17507 		extend(this, {
17508 			execCommand: execCommand,
17509 			queryCommandState: queryCommandState,
17510 			queryCommandValue: queryCommandValue,
17511 			addCommands: addCommands
17512 		});
17513 
17514 		// Private methods
17515 
17516 		function execNativeCommand(command, ui, value) {
17517 			if (ui === undefined) {
17518 				ui = FALSE;
17519 			}
17520 
17521 			if (value === undefined) {
17522 				value = null;
17523 			}
17524 
17525 			return editor.getDoc().execCommand(command, ui, value);
17526 		}
17527 
17528 		function isFormatMatch(name) {
17529 			return formatter.match(name);
17530 		}
17531 
17532 		function toggleFormat(name, value) {
17533 			formatter.toggle(name, value ? {value: value} : undefined);
17534 			editor.nodeChanged();
17535 		}
17536 
17537 		function storeSelection(type) {
17538 			bookmark = selection.getBookmark(type);
17539 		}
17540 
17541 		function restoreSelection() {
17542 			selection.moveToBookmark(bookmark);
17543 		}
17544 
17545 		// Add execCommand overrides
17546 		addCommands({
17547 			// Ignore these, added for compatibility
17548 			'mceResetDesignMode,mceBeginUndoLevel': function() {},
17549 
17550 			// Add undo manager logic
17551 			'mceEndUndoLevel,mceAddUndoLevel': function() {
17552 				editor.undoManager.add();
17553 			},
17554 
17555 			'Cut,Copy,Paste': function(command) {
17556 				var doc = editor.getDoc(), failed;
17557 
17558 				// Try executing the native command
17559 				try {
17560 					execNativeCommand(command);
17561 				} catch (ex) {
17562 					// Command failed
17563 					failed = TRUE;
17564 				}
17565 
17566 				// Present alert message about clipboard access not being available
17567 				if (failed || !doc.queryCommandSupported(command)) {
17568 					var msg = editor.translate(
17569 						"Your browser doesn't support direct access to the clipboard. " +
17570 						"Please use the Ctrl+X/C/V keyboard shortcuts instead."
17571 					);
17572 
17573 					if (Env.mac) {
17574 						msg = msg.replace(/Ctrl\+/g, '\u2318+');
17575 					}
17576 
17577 					editor.windowManager.alert(msg);
17578 				}
17579 			},
17580 
17581 			// Override unlink command
17582 			unlink: function() {
17583 				if (selection.isCollapsed()) {
17584 					var elm = selection.getNode();
17585 					if (elm.tagName == 'A') {
17586 						editor.dom.remove(elm, true);
17587 					}
17588 
17589 					return;
17590 				}
17591 
17592 				formatter.remove("link");
17593 			},
17594 
17595 			// Override justify commands to use the text formatter engine
17596 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
17597 				var align = command.substring(7);
17598 
17599 				if (align == 'full') {
17600 					align = 'justify';
17601 				}
17602 
17603 				// Remove all other alignments first
17604 				each('left,center,right,justify'.split(','), function(name) {
17605 					if (align != name) {
17606 						formatter.remove('align' + name);
17607 					}
17608 				});
17609 
17610 				toggleFormat('align' + align);
17611 				execCommand('mceRepaint');
17612 			},
17613 
17614 			// Override list commands to fix WebKit bug
17615 			'InsertUnorderedList,InsertOrderedList': function(command) {
17616 				var listElm, listParent;
17617 
17618 				execNativeCommand(command);
17619 
17620 				// WebKit produces lists within block elements so we need to split them
17621 				// we will replace the native list creation logic to custom logic later on
17622 				// TODO: Remove this when the list creation logic is removed
17623 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
17624 				if (listElm) {
17625 					listParent = listElm.parentNode;
17626 
17627 					// If list is within a text block then split that block
17628 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
17629 						storeSelection();
17630 						dom.split(listParent, listElm);
17631 						restoreSelection();
17632 					}
17633 				}
17634 			},
17635 
17636 			// Override commands to use the text formatter engine
17637 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
17638 				toggleFormat(command);
17639 			},
17640 
17641 			// Override commands to use the text formatter engine
17642 			'ForeColor,HiliteColor,FontName': function(command, ui, value) {
17643 				toggleFormat(command, value);
17644 			},
17645 
17646 			FontSize: function(command, ui, value) {
17647 				var fontClasses, fontSizes;
17648 
17649 				// Convert font size 1-7 to styles
17650 				if (value >= 1 && value <= 7) {
17651 					fontSizes = explode(settings.font_size_style_values);
17652 					fontClasses = explode(settings.font_size_classes);
17653 
17654 					if (fontClasses) {
17655 						value = fontClasses[value - 1] || value;
17656 					} else {
17657 						value = fontSizes[value - 1] || value;
17658 					}
17659 				}
17660 
17661 				toggleFormat(command, value);
17662 			},
17663 
17664 			RemoveFormat: function(command) {
17665 				formatter.remove(command);
17666 			},
17667 
17668 			mceBlockQuote: function() {
17669 				toggleFormat('blockquote');
17670 			},
17671 
17672 			FormatBlock: function(command, ui, value) {
17673 				return toggleFormat(value || 'p');
17674 			},
17675 
17676 			mceCleanup: function() {
17677 				var bookmark = selection.getBookmark();
17678 
17679 				editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
17680 
17681 				selection.moveToBookmark(bookmark);
17682 			},
17683 
17684 			mceRemoveNode: function(command, ui, value) {
17685 				var node = value || selection.getNode();
17686 
17687 				// Make sure that the body node isn't removed
17688 				if (node != editor.getBody()) {
17689 					storeSelection();
17690 					editor.dom.remove(node, TRUE);
17691 					restoreSelection();
17692 				}
17693 			},
17694 
17695 			mceSelectNodeDepth: function(command, ui, value) {
17696 				var counter = 0;
17697 
17698 				dom.getParent(selection.getNode(), function(node) {
17699 					if (node.nodeType == 1 && counter++ == value) {
17700 						selection.select(node);
17701 						return FALSE;
17702 					}
17703 				}, editor.getBody());
17704 			},
17705 
17706 			mceSelectNode: function(command, ui, value) {
17707 				selection.select(value);
17708 			},
17709 
17710 			mceInsertContent: function(command, ui, value) {
17711 				var parser, serializer, parentNode, rootNode, fragment, args;
17712 				var marker, rng, node, node2, bookmarkHtml, merge;
17713 				var textInlineElements = editor.schema.getTextInlineElements();
17714 
17715 				function trimOrPaddLeftRight(html) {
17716 					var rng, container, offset;
17717 
17718 					rng = selection.getRng(true);
17719 					container = rng.startContainer;
17720 					offset = rng.startOffset;
17721 
17722 					function hasSiblingText(siblingName) {
17723 						return container[siblingName] && container[siblingName].nodeType == 3;
17724 					}
17725 
17726 					if (container.nodeType == 3) {
17727 						if (offset > 0) {
17728 							html = html.replace(/^ /, ' ');
17729 						} else if (!hasSiblingText('previousSibling')) {
17730 							html = html.replace(/^ /, ' ');
17731 						}
17732 
17733 						if (offset < container.length) {
17734 							html = html.replace(/ (<br>|)$/, ' ');
17735 						} else if (!hasSiblingText('nextSibling')) {
17736 							html = html.replace(/( | )(<br>|)$/, ' ');
17737 						}
17738 					}
17739 
17740 					return html;
17741 				}
17742 
17743 				function markInlineFormatElements(fragment) {
17744 					if (merge) {
17745 						for (node = fragment.firstChild; node; node = node.walk(true)) {
17746 							if (textInlineElements[node.name]) {
17747 								node.attr('data-mce-new', "true");
17748 							}
17749 						}
17750 					}
17751 				}
17752 
17753 				function reduceInlineTextElements() {
17754 					if (merge) {
17755 						var root = editor.getBody(), elementUtils = new ElementUtils(dom);
17756 
17757 						each(dom.select('*[data-mce-new]'), function(node) {
17758 							node.removeAttribute('data-mce-new');
17759 
17760 							for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
17761 								if (elementUtils.compare(testNode, node)) {
17762 									dom.remove(node, true);
17763 								}
17764 							}
17765 						});
17766 					}
17767 				}
17768 
17769 				if (typeof(value) != 'string') {
17770 					merge = value.merge;
17771 					value = value.content;
17772 				}
17773 
17774 				// Check for whitespace before/after value
17775 				if (/^ | $/.test(value)) {
17776 					value = trimOrPaddLeftRight(value);
17777 				}
17778 
17779 				// Setup parser and serializer
17780 				parser = editor.parser;
17781 				serializer = new Serializer({}, editor.schema);
17782 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">​</span>';
17783 
17784 				// Run beforeSetContent handlers on the HTML to be inserted
17785 				args = {content: value, format: 'html', selection: true};
17786 				editor.fire('BeforeSetContent', args);
17787 				value = args.content;
17788 
17789 				// Add caret at end of contents if it's missing
17790 				if (value.indexOf('{$caret}') == -1) {
17791 					value += '{$caret}';
17792 				}
17793 
17794 				// Replace the caret marker with a span bookmark element
17795 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
17796 
17797 				// If selection is at <body>|<p></p> then move it into <body><p>|</p>
17798 				rng = selection.getRng();
17799 				var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
17800 				var body = editor.getBody();
17801 				if (caretElement === body && selection.isCollapsed()) {
17802 					if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
17803 						rng = dom.createRng();
17804 						rng.setStart(body.firstChild, 0);
17805 						rng.setEnd(body.firstChild, 0);
17806 						selection.setRng(rng);
17807 					}
17808 				}
17809 
17810 				// Insert node maker where we will insert the new HTML and get it's parent
17811 				if (!selection.isCollapsed()) {
17812 					editor.getDoc().execCommand('Delete', false, null);
17813 				}
17814 
17815 				parentNode = selection.getNode();
17816 
17817 				// Parse the fragment within the context of the parent node
17818 				var parserArgs = {context: parentNode.nodeName.toLowerCase()};
17819 				fragment = parser.parse(value, parserArgs);
17820 
17821 				markInlineFormatElements(fragment);
17822 
17823 				// Move the caret to a more suitable location
17824 				node = fragment.lastChild;
17825 				if (node.attr('id') == 'mce_marker') {
17826 					marker = node;
17827 
17828 					for (node = node.prev; node; node = node.walk(true)) {
17829 						if (node.type == 3 || !dom.isBlock(node.name)) {
17830 							if (editor.schema.isValidChild(node.parent.name, 'span')) {
17831 								node.parent.insert(marker, node, node.name === 'br');
17832 							}
17833 							break;
17834 						}
17835 					}
17836 				}
17837 
17838 				// If parser says valid we can insert the contents into that parent
17839 				if (!parserArgs.invalid) {
17840 					value = serializer.serialize(fragment);
17841 
17842 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
17843 					node = parentNode.firstChild;
17844 					node2 = parentNode.lastChild;
17845 					if (!node || (node === node2 && node.nodeName === 'BR')) {
17846 						dom.setHTML(parentNode, value);
17847 					} else {
17848 						selection.setContent(value);
17849 					}
17850 				} else {
17851 					// If the fragment was invalid within that context then we need
17852 					// to parse and process the parent it's inserted into
17853 
17854 					// Insert bookmark node and get the parent
17855 					selection.setContent(bookmarkHtml);
17856 					parentNode = selection.getNode();
17857 					rootNode = editor.getBody();
17858 
17859 					// Opera will return the document node when selection is in root
17860 					if (parentNode.nodeType == 9) {
17861 						parentNode = node = rootNode;
17862 					} else {
17863 						node = parentNode;
17864 					}
17865 
17866 					// Find the ancestor just before the root element
17867 					while (node !== rootNode) {
17868 						parentNode = node;
17869 						node = node.parentNode;
17870 					}
17871 
17872 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
17873 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
17874 					value = serializer.serialize(
17875 						parser.parse(
17876 							// Need to replace by using a function since $ in the contents would otherwise be a problem
17877 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
17878 								return serializer.serialize(fragment);
17879 							})
17880 						)
17881 					);
17882 
17883 					// Set the inner/outer HTML depending on if we are in the root or not
17884 					if (parentNode == rootNode) {
17885 						dom.setHTML(rootNode, value);
17886 					} else {
17887 						dom.setOuterHTML(parentNode, value);
17888 					}
17889 				}
17890 
17891 				reduceInlineTextElements();
17892 
17893 				marker = dom.get('mce_marker');
17894 				selection.scrollIntoView(marker);
17895 
17896 				// Move selection before marker and remove it
17897 				rng = dom.createRng();
17898 
17899 				// If previous sibling is a text node set the selection to the end of that node
17900 				node = marker.previousSibling;
17901 				if (node && node.nodeType == 3) {
17902 					rng.setStart(node, node.nodeValue.length);
17903 
17904 					// TODO: Why can't we normalize on IE
17905 					if (!isIE) {
17906 						node2 = marker.nextSibling;
17907 						if (node2 && node2.nodeType == 3) {
17908 							node.appendData(node2.data);
17909 							node2.parentNode.removeChild(node2);
17910 						}
17911 					}
17912 				} else {
17913 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
17914 					rng.setStartBefore(marker);
17915 					rng.setEndBefore(marker);
17916 				}
17917 
17918 				// Remove the marker node and set the new range
17919 				dom.remove(marker);
17920 				selection.setRng(rng);
17921 
17922 				// Dispatch after event and add any visual elements needed
17923 				editor.fire('SetContent', args);
17924 				editor.addVisual();
17925 			},
17926 
17927 			mceInsertRawHTML: function(command, ui, value) {
17928 				selection.setContent('tiny_mce_marker');
17929 				editor.setContent(
17930 					editor.getContent().replace(/tiny_mce_marker/g, function() {
17931 						return value;
17932 					})
17933 				);
17934 			},
17935 
17936 			mceToggleFormat: function(command, ui, value) {
17937 				toggleFormat(value);
17938 			},
17939 
17940 			mceSetContent: function(command, ui, value) {
17941 				editor.setContent(value);
17942 			},
17943 
17944 			'Indent,Outdent': function(command) {
17945 				var intentValue, indentUnit, value;
17946 
17947 				// Setup indent level
17948 				intentValue = settings.indentation;
17949 				indentUnit = /[a-z%]+$/i.exec(intentValue);
17950 				intentValue = parseInt(intentValue, 10);
17951 
17952 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
17953 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
17954 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
17955 						formatter.apply('div');
17956 					}
17957 
17958 					each(selection.getSelectedBlocks(), function(element) {
17959 						if (element.nodeName != "LI") {
17960 							var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
17961 
17962 							indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
17963 
17964 							if (command == 'outdent') {
17965 								value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
17966 								dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
17967 							} else {
17968 								value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
17969 								dom.setStyle(element, indentStyleName, value);
17970 							}
17971 						}
17972 					});
17973 				} else {
17974 					execNativeCommand(command);
17975 				}
17976 			},
17977 
17978 			mceRepaint: function() {
17979 				if (isGecko) {
17980 					try {
17981 						storeSelection(TRUE);
17982 
17983 						if (selection.getSel()) {
17984 							selection.getSel().selectAllChildren(editor.getBody());
17985 						}
17986 
17987 						selection.collapse(TRUE);
17988 						restoreSelection();
17989 					} catch (ex) {
17990 						// Ignore
17991 					}
17992 				}
17993 			},
17994 
17995 			InsertHorizontalRule: function() {
17996 				editor.execCommand('mceInsertContent', false, '<hr />');
17997 			},
17998 
17999 			mceToggleVisualAid: function() {
18000 				editor.hasVisual = !editor.hasVisual;
18001 				editor.addVisual();
18002 			},
18003 
18004 			mceReplaceContent: function(command, ui, value) {
18005 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
18006 			},
18007 
18008 			mceInsertLink: function(command, ui, value) {
18009 				var anchor;
18010 
18011 				if (typeof(value) == 'string') {
18012 					value = {href: value};
18013 				}
18014 
18015 				anchor = dom.getParent(selection.getNode(), 'a');
18016 
18017 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
18018 				value.href = value.href.replace(' ', '%20');
18019 
18020 				// Remove existing links if there could be child links or that the href isn't specified
18021 				if (!anchor || !value.href) {
18022 					formatter.remove('link');
18023 				}
18024 
18025 				// Apply new link to selection
18026 				if (value.href) {
18027 					formatter.apply('link', value, anchor);
18028 				}
18029 			},
18030 
18031 			selectAll: function() {
18032 				var root = dom.getRoot(), rng;
18033 
18034 				if (selection.getRng().setStart) {
18035 					rng = dom.createRng();
18036 					rng.setStart(root, 0);
18037 					rng.setEnd(root, root.childNodes.length);
18038 					selection.setRng(rng);
18039 				} else {
18040 					// IE will render it's own root level block elements and sometimes
18041 					// even put font elements in them when the user starts typing. So we need to
18042 					// move the selection to a more suitable element from this:
18043 					// <body>|<p></p></body> to this: <body><p>|</p></body>
18044 					rng = selection.getRng();
18045 					if (!rng.item) {
18046 						rng.moveToElementText(root);
18047 						rng.select();
18048 					}
18049 				}
18050 			},
18051 
18052 			"delete": function() {
18053 				execNativeCommand("Delete");
18054 
18055 				// Check if body is empty after the delete call if so then set the contents
18056 				// to an empty string and move the caret to any block produced by that operation
18057 				// this fixes the issue with root blocks not being properly produced after a delete call on IE
18058 				var body = editor.getBody();
18059 
18060 				if (dom.isEmpty(body)) {
18061 					editor.setContent('');
18062 
18063 					if (body.firstChild && dom.isBlock(body.firstChild)) {
18064 						editor.selection.setCursorLocation(body.firstChild, 0);
18065 					} else {
18066 						editor.selection.setCursorLocation(body, 0);
18067 					}
18068 				}
18069 			},
18070 
18071 			mceNewDocument: function() {
18072 				editor.setContent('');
18073 			},
18074 
18075 			InsertLineBreak: function(command, ui, value) {
18076 				// We load the current event in from EnterKey.js when appropriate to heed
18077 				// certain event-specific variations such as ctrl-enter in a list
18078 				var evt = value;
18079 				var brElm, extraBr, marker;
18080 				var rng = selection.getRng(true);
18081 				new RangeUtils(dom).normalize(rng);
18082 
18083 				var offset = rng.startOffset;
18084 				var container = rng.startContainer;
18085 
18086 				// Resolve node index
18087 				if (container.nodeType == 1 && container.hasChildNodes()) {
18088 					var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18089 
18090 					container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18091 					if (isAfterLastNodeInContainer && container.nodeType == 3) {
18092 						offset = container.nodeValue.length;
18093 					} else {
18094 						offset = 0;
18095 					}
18096 				}
18097 
18098 				var parentBlock = dom.getParent(container, dom.isBlock);
18099 				var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18100 				var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18101 				var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18102 
18103 				// Enter inside block contained within a LI then split or insert before/after LI
18104 				var isControlKey = evt && evt.ctrlKey;
18105 				if (containerBlockName == 'LI' && !isControlKey) {
18106 					parentBlock = containerBlock;
18107 					parentBlockName = containerBlockName;
18108 				}
18109 
18110 				// Walks the parent block to the right and look for BR elements
18111 				function hasRightSideContent() {
18112 					var walker = new TreeWalker(container, parentBlock), node;
18113 					var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18114 
18115 					while ((node = walker.next())) {
18116 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
18117 							return true;
18118 						}
18119 					}
18120 				}
18121 
18122 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18123 					// Insert extra BR element at the end block elements
18124 					if (!isOldIE && !hasRightSideContent()) {
18125 						brElm = dom.create('br');
18126 						rng.insertNode(brElm);
18127 						rng.setStartAfter(brElm);
18128 						rng.setEndAfter(brElm);
18129 						extraBr = true;
18130 					}
18131 				}
18132 
18133 				brElm = dom.create('br');
18134 				rng.insertNode(brElm);
18135 
18136 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18137 				var documentMode = dom.doc.documentMode;
18138 				if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18139 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18140 				}
18141 
18142 				// Insert temp marker and scroll to that
18143 				marker = dom.create('span', {}, ' ');
18144 				brElm.parentNode.insertBefore(marker, brElm);
18145 				selection.scrollIntoView(marker);
18146 				dom.remove(marker);
18147 
18148 				if (!extraBr) {
18149 					rng.setStartAfter(brElm);
18150 					rng.setEndAfter(brElm);
18151 				} else {
18152 					rng.setStartBefore(brElm);
18153 					rng.setEndBefore(brElm);
18154 				}
18155 
18156 				selection.setRng(rng);
18157 				editor.undoManager.add();
18158 
18159 				return TRUE;
18160 			}
18161 		});
18162 
18163 		// Add queryCommandState overrides
18164 		addCommands({
18165 			// Override justify commands
18166 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
18167 				var name = 'align' + command.substring(7);
18168 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
18169 				var matches = map(nodes, function(node) {
18170 					return !!formatter.matchNode(node, name);
18171 				});
18172 				return inArray(matches, TRUE) !== -1;
18173 			},
18174 
18175 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
18176 				return isFormatMatch(command);
18177 			},
18178 
18179 			mceBlockQuote: function() {
18180 				return isFormatMatch('blockquote');
18181 			},
18182 
18183 			Outdent: function() {
18184 				var node;
18185 
18186 				if (settings.inline_styles) {
18187 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
18188 						return TRUE;
18189 					}
18190 
18191 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
18192 						return TRUE;
18193 					}
18194 				}
18195 
18196 				return (
18197 					queryCommandState('InsertUnorderedList') ||
18198 					queryCommandState('InsertOrderedList') ||
18199 					(!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
18200 				);
18201 			},
18202 
18203 			'InsertUnorderedList,InsertOrderedList': function(command) {
18204 				var list = dom.getParent(selection.getNode(), 'ul,ol');
18205 
18206 				return list &&
18207 					(
18208 						command === 'insertunorderedlist' && list.tagName === 'UL' ||
18209 						command === 'insertorderedlist' && list.tagName === 'OL'
18210 					);
18211 			}
18212 		}, 'state');
18213 
18214 		// Add queryCommandValue overrides
18215 		addCommands({
18216 			'FontSize,FontName': function(command) {
18217 				var value = 0, parent;
18218 
18219 				if ((parent = dom.getParent(selection.getNode(), 'span'))) {
18220 					if (command == 'fontsize') {
18221 						value = parent.style.fontSize;
18222 					} else {
18223 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
18224 					}
18225 				}
18226 
18227 				return value;
18228 			}
18229 		}, 'value');
18230 
18231 		// Add undo manager logic
18232 		addCommands({
18233 			Undo: function() {
18234 				editor.undoManager.undo();
18235 			},
18236 
18237 			Redo: function() {
18238 				editor.undoManager.redo();
18239 			}
18240 		});
18241 	};
18242 });
18243 
18244 // Included from: js/tinymce/classes/util/URI.js
18245 
18246 /**
18247  * URI.js
18248  *
18249  * Copyright, Moxiecode Systems AB
18250  * Released under LGPL License.
18251  *
18252  * License: http://www.tinymce.com/license
18253  * Contributing: http://www.tinymce.com/contributing
18254  */
18255 
18256 /**
18257  * This class handles parsing, modification and serialization of URI/URL strings.
18258  * @class tinymce.util.URI
18259  */
18260 define("tinymce/util/URI", [
18261 	"tinymce/util/Tools"
18262 ], function(Tools) {
18263 	var each = Tools.each, trim = Tools.trim;
18264 	var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' ');
18265 	var DEFAULT_PORTS = {
18266 		'ftp': 21,
18267 		'http': 80,
18268 		'https': 443,
18269 		'mailto': 25
18270 	};
18271 
18272 	/**
18273 	 * Constructs a new URI instance.
18274 	 *
18275 	 * @constructor
18276 	 * @method URI
18277 	 * @param {String} url URI string to parse.
18278 	 * @param {Object} settings Optional settings object.
18279 	 */
18280 	function URI(url, settings) {
18281 		var self = this, baseUri, base_url;
18282 
18283 		url = trim(url);
18284 		settings = self.settings = settings || {};
18285 		baseUri = settings.base_uri;
18286 
18287 		// Strange app protocol that isn't http/https or local anchor
18288 		// For example: mailto,skype,tel etc.
18289 		if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
18290 			self.source = url;
18291 			return;
18292 		}
18293 
18294 		var isProtocolRelative = url.indexOf('//') === 0;
18295 
18296 		// Absolute path with no host, fake host and protocol
18297 		if (url.indexOf('/') === 0 && !isProtocolRelative) {
18298 			url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
18299 		}
18300 
18301 		// Relative path http:// or protocol relative //path
18302 		if (!/^[\w\-]*:?\/\//.test(url)) {
18303 			base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
18304 			if (settings.base_uri.protocol === "") {
18305 				url = '//mce_host' + self.toAbsPath(base_url, url);
18306 			} else {
18307 				url = /([^#?]*)([#?]?.*)/.exec(url);
18308 				url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
18309 			}
18310 		}
18311 
18312 		// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
18313 		url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
18314 
18315 		/*jshint maxlen: 255 */
18316 		/*eslint max-len: 0 */
18317 		url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
18318 
18319 		each(queryParts, function(v, i) {
18320 			var part = url[i];
18321 
18322 			// Zope 3 workaround, they use @@something
18323 			if (part) {
18324 				part = part.replace(/\(mce_at\)/g, '@@');
18325 			}
18326 
18327 			self[v] = part;
18328 		});
18329 
18330 		if (baseUri) {
18331 			if (!self.protocol) {
18332 				self.protocol = baseUri.protocol;
18333 			}
18334 
18335 			if (!self.userInfo) {
18336 				self.userInfo = baseUri.userInfo;
18337 			}
18338 
18339 			if (!self.port && self.host === 'mce_host') {
18340 				self.port = baseUri.port;
18341 			}
18342 
18343 			if (!self.host || self.host === 'mce_host') {
18344 				self.host = baseUri.host;
18345 			}
18346 
18347 			self.source = '';
18348 		}
18349 
18350 		if (isProtocolRelative) {
18351 			self.protocol = '';
18352 		}
18353 
18354 		//t.path = t.path || '/';
18355 	}
18356 
18357 	URI.prototype = {
18358 		/**
18359 		 * Sets the internal path part of the URI.
18360 		 *
18361 		 * @method setPath
18362 		 * @param {string} path Path string to set.
18363 		 */
18364 		setPath: function(path) {
18365 			var self = this;
18366 
18367 			path = /^(.*?)\/?(\w+)?$/.exec(path);
18368 
18369 			// Update path parts
18370 			self.path = path[0];
18371 			self.directory = path[1];
18372 			self.file = path[2];
18373 
18374 			// Rebuild source
18375 			self.source = '';
18376 			self.getURI();
18377 		},
18378 
18379 		/**
18380 		 * Converts the specified URI into a relative URI based on the current URI instance location.
18381 		 *
18382 		 * @method toRelative
18383 		 * @param {String} uri URI to convert into a relative path/URI.
18384 		 * @return {String} Relative URI from the point specified in the current URI instance.
18385 		 * @example
18386 		 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
18387 		 * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
18388 		 */
18389 		toRelative: function(uri) {
18390 			var self = this, output;
18391 
18392 			if (uri === "./") {
18393 				return uri;
18394 			}
18395 
18396 			uri = new URI(uri, {base_uri: self});
18397 
18398 			// Not on same domain/port or protocol
18399 			if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
18400 				(self.protocol != uri.protocol && uri.protocol !== "")) {
18401 				return uri.getURI();
18402 			}
18403 
18404 			var tu = self.getURI(), uu = uri.getURI();
18405 
18406 			// Allow usage of the base_uri when relative_urls = true
18407 			if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
18408 				return tu;
18409 			}
18410 
18411 			output = self.toRelPath(self.path, uri.path);
18412 
18413 			// Add query
18414 			if (uri.query) {
18415 				output += '?' + uri.query;
18416 			}
18417 
18418 			// Add anchor
18419 			if (uri.anchor) {
18420 				output += '#' + uri.anchor;
18421 			}
18422 
18423 			return output;
18424 		},
18425 
18426 		/**
18427 		 * Converts the specified URI into a absolute URI based on the current URI instance location.
18428 		 *
18429 		 * @method toAbsolute
18430 		 * @param {String} uri URI to convert into a relative path/URI.
18431 		 * @param {Boolean} noHost No host and protocol prefix.
18432 		 * @return {String} Absolute URI from the point specified in the current URI instance.
18433 		 * @example
18434 		 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
18435 		 * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
18436 		 */
18437 		toAbsolute: function(uri, noHost) {
18438 			uri = new URI(uri, {base_uri: this});
18439 
18440 			return uri.getURI(noHost && this.isSameOrigin(uri));
18441 		},
18442 
18443 		/**
18444 		 * Determine whether the given URI has the same origin as this URI.  Based on RFC-6454.
18445 		 * Supports default ports for protocols listed in DEFAULT_PORTS.  Unsupported protocols will fail safe: they
18446 		 * won't match, if the port specifications differ.
18447 		 *
18448 		 * @method isSameOrigin
18449 		 * @param {tinymce.util.URI} uri Uri instance to compare.
18450 		 * @returns {Boolean} True if the origins are the same.
18451 		 */
18452 		isSameOrigin: function(uri) {
18453 			if (this.host == uri.host && this.protocol == uri.protocol) {
18454 				if (this.port == uri.port) {
18455 					return true;
18456 				}
18457 
18458 				var defaultPort = DEFAULT_PORTS[this.protocol];
18459 				if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
18460 					return true;
18461 				}
18462 			}
18463 
18464 			return false;
18465 		},
18466 
18467 		/**
18468 		 * Converts a absolute path into a relative path.
18469 		 *
18470 		 * @method toRelPath
18471 		 * @param {String} base Base point to convert the path from.
18472 		 * @param {String} path Absolute path to convert into a relative path.
18473 		 */
18474 		toRelPath: function(base, path) {
18475 			var items, breakPoint = 0, out = '', i, l;
18476 
18477 			// Split the paths
18478 			base = base.substring(0, base.lastIndexOf('/'));
18479 			base = base.split('/');
18480 			items = path.split('/');
18481 
18482 			if (base.length >= items.length) {
18483 				for (i = 0, l = base.length; i < l; i++) {
18484 					if (i >= items.length || base[i] != items[i]) {
18485 						breakPoint = i + 1;
18486 						break;
18487 					}
18488 				}
18489 			}
18490 
18491 			if (base.length < items.length) {
18492 				for (i = 0, l = items.length; i < l; i++) {
18493 					if (i >= base.length || base[i] != items[i]) {
18494 						breakPoint = i + 1;
18495 						break;
18496 					}
18497 				}
18498 			}
18499 
18500 			if (breakPoint === 1) {
18501 				return path;
18502 			}
18503 
18504 			for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
18505 				out += "../";
18506 			}
18507 
18508 			for (i = breakPoint - 1, l = items.length; i < l; i++) {
18509 				if (i != breakPoint - 1) {
18510 					out += "/" + items[i];
18511 				} else {
18512 					out += items[i];
18513 				}
18514 			}
18515 
18516 			return out;
18517 		},
18518 
18519 		/**
18520 		 * Converts a relative path into a absolute path.
18521 		 *
18522 		 * @method toAbsPath
18523 		 * @param {String} base Base point to convert the path from.
18524 		 * @param {String} path Relative path to convert into an absolute path.
18525 		 */
18526 		toAbsPath: function(base, path) {
18527 			var i, nb = 0, o = [], tr, outPath;
18528 
18529 			// Split paths
18530 			tr = /\/$/.test(path) ? '/' : '';
18531 			base = base.split('/');
18532 			path = path.split('/');
18533 
18534 			// Remove empty chunks
18535 			each(base, function(k) {
18536 				if (k) {
18537 					o.push(k);
18538 				}
18539 			});
18540 
18541 			base = o;
18542 
18543 			// Merge relURLParts chunks
18544 			for (i = path.length - 1, o = []; i >= 0; i--) {
18545 				// Ignore empty or .
18546 				if (path[i].length === 0 || path[i] === ".") {
18547 					continue;
18548 				}
18549 
18550 				// Is parent
18551 				if (path[i] === '..') {
18552 					nb++;
18553 					continue;
18554 				}
18555 
18556 				// Move up
18557 				if (nb > 0) {
18558 					nb--;
18559 					continue;
18560 				}
18561 
18562 				o.push(path[i]);
18563 			}
18564 
18565 			i = base.length - nb;
18566 
18567 			// If /a/b/c or /
18568 			if (i <= 0) {
18569 				outPath = o.reverse().join('/');
18570 			} else {
18571 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
18572 			}
18573 
18574 			// Add front / if it's needed
18575 			if (outPath.indexOf('/') !== 0) {
18576 				outPath = '/' + outPath;
18577 			}
18578 
18579 			// Add traling / if it's needed
18580 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
18581 				outPath += tr;
18582 			}
18583 
18584 			return outPath;
18585 		},
18586 
18587 		/**
18588 		 * Returns the full URI of the internal structure.
18589 		 *
18590 		 * @method getURI
18591 		 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
18592 		 */
18593 		getURI: function(noProtoHost) {
18594 			var s, self = this;
18595 
18596 			// Rebuild source
18597 			if (!self.source || noProtoHost) {
18598 				s = '';
18599 
18600 				if (!noProtoHost) {
18601 					if (self.protocol) {
18602 						s += self.protocol + '://';
18603 					} else {
18604 						s += '//';
18605 					}
18606 
18607 					if (self.userInfo) {
18608 						s += self.userInfo + '@';
18609 					}
18610 
18611 					if (self.host) {
18612 						s += self.host;
18613 					}
18614 
18615 					if (self.port) {
18616 						s += ':' + self.port;
18617 					}
18618 				}
18619 
18620 				if (self.path) {
18621 					s += self.path;
18622 				}
18623 
18624 				if (self.query) {
18625 					s += '?' + self.query;
18626 				}
18627 
18628 				if (self.anchor) {
18629 					s += '#' + self.anchor;
18630 				}
18631 
18632 				self.source = s;
18633 			}
18634 
18635 			return self.source;
18636 		}
18637 	};
18638 
18639 	return URI;
18640 });
18641 
18642 // Included from: js/tinymce/classes/util/Class.js
18643 
18644 /**
18645  * Class.js
18646  *
18647  * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
18648  */
18649 
18650 /**
18651  * This utilitiy class is used for easier inheritage.
18652  *
18653  * Features:
18654  * * Exposed super functions: this._super();
18655  * * Mixins
18656  * * Dummy functions
18657  * * Property functions: var value = object.value(); and object.value(newValue);
18658  * * Static functions
18659  * * Defaults settings
18660  */
18661 define("tinymce/util/Class", [
18662 	"tinymce/util/Tools"
18663 ], function(Tools) {
18664 	var each = Tools.each, extend = Tools.extend;
18665 
18666 	var extendClass, initializing;
18667 
18668 	function Class() {
18669 	}
18670 
18671 	// Provides classical inheritance, based on code made by John Resig
18672 	Class.extend = extendClass = function(prop) {
18673 		var self = this, _super = self.prototype, prototype, name, member;
18674 
18675 		// The dummy class constructor
18676 		function Class() {
18677 			var i, mixins, mixin, self = this;
18678 
18679 			// All construction is actually done in the init method
18680 			if (!initializing) {
18681 				// Run class constuctor
18682 				if (self.init) {
18683 					self.init.apply(self, arguments);
18684 				}
18685 
18686 				// Run mixin constructors
18687 				mixins = self.Mixins;
18688 				if (mixins) {
18689 					i = mixins.length;
18690 					while (i--) {
18691 						mixin = mixins[i];
18692 						if (mixin.init) {
18693 							mixin.init.apply(self, arguments);
18694 						}
18695 					}
18696 				}
18697 			}
18698 		}
18699 
18700 		// Dummy function, needs to be extended in order to provide functionality
18701 		function dummy() {
18702 			return this;
18703 		}
18704 
18705 		// Creates a overloaded method for the class
18706 		// this enables you to use this._super(); to call the super function
18707 		function createMethod(name, fn) {
18708 			return function() {
18709 				var self = this, tmp = self._super, ret;
18710 
18711 				self._super = _super[name];
18712 				ret = fn.apply(self, arguments);
18713 				self._super = tmp;
18714 
18715 				return ret;
18716 			};
18717 		}
18718 
18719 		// Instantiate a base class (but only create the instance,
18720 		// don't run the init constructor)
18721 		initializing = true;
18722 
18723 		/*eslint new-cap:0 */
18724 		prototype = new self();
18725 		initializing = false;
18726 
18727 		// Add mixins
18728 		if (prop.Mixins) {
18729 			each(prop.Mixins, function(mixin) {
18730 				mixin = mixin;
18731 
18732 				for (var name in mixin) {
18733 					if (name !== "init") {
18734 						prop[name] = mixin[name];
18735 					}
18736 				}
18737 			});
18738 
18739 			if (_super.Mixins) {
18740 				prop.Mixins = _super.Mixins.concat(prop.Mixins);
18741 			}
18742 		}
18743 
18744 		// Generate dummy methods
18745 		if (prop.Methods) {
18746 			each(prop.Methods.split(','), function(name) {
18747 				prop[name] = dummy;
18748 			});
18749 		}
18750 
18751 		// Generate property methods
18752 		if (prop.Properties) {
18753 			each(prop.Properties.split(','), function(name) {
18754 				var fieldName = '_' + name;
18755 
18756 				prop[name] = function(value) {
18757 					var self = this, undef;
18758 
18759 					// Set value
18760 					if (value !== undef) {
18761 						self[fieldName] = value;
18762 
18763 						return self;
18764 					}
18765 
18766 					// Get value
18767 					return self[fieldName];
18768 				};
18769 			});
18770 		}
18771 
18772 		// Static functions
18773 		if (prop.Statics) {
18774 			each(prop.Statics, function(func, name) {
18775 				Class[name] = func;
18776 			});
18777 		}
18778 
18779 		// Default settings
18780 		if (prop.Defaults && _super.Defaults) {
18781 			prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
18782 		}
18783 
18784 		// Copy the properties over onto the new prototype
18785 		for (name in prop) {
18786 			member = prop[name];
18787 
18788 			if (typeof member == "function" && _super[name]) {
18789 				prototype[name] = createMethod(name, member);
18790 			} else {
18791 				prototype[name] = member;
18792 			}
18793 		}
18794 
18795 		// Populate our constructed prototype object
18796 		Class.prototype = prototype;
18797 
18798 		// Enforce the constructor to be what we expect
18799 		Class.constructor = Class;
18800 
18801 		// And make this class extendible
18802 		Class.extend = extendClass;
18803 
18804 		return Class;
18805 	};
18806 
18807 	return Class;
18808 });
18809 
18810 // Included from: js/tinymce/classes/util/EventDispatcher.js
18811 
18812 /**
18813  * EventDispatcher.js
18814  *
18815  * Copyright, Moxiecode Systems AB
18816  * Released under LGPL License.
18817  *
18818  * License: http://www.tinymce.com/license
18819  * Contributing: http://www.tinymce.com/contributing
18820  */
18821 
18822 /**
18823  * This class lets you add/remove and fire events by name on the specified scope. This makes
18824  * it easy to add event listener logic to any class.
18825  *
18826  * @class tinymce.util.EventDispatcher
18827  * @example
18828  *  var eventDispatcher = new EventDispatcher();
18829  *
18830  *  eventDispatcher.on('click', function() {console.log('data');});
18831  *  eventDispatcher.fire('click', {data: 123});
18832  */
18833 define("tinymce/util/EventDispatcher", [
18834 	"tinymce/util/Tools"
18835 ], function(Tools) {
18836 	var nativeEvents = Tools.makeMap(
18837 		"focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
18838 		"mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
18839 		"draggesture dragdrop drop drag submit " +
18840 		"compositionstart compositionend compositionupdate touchstart touchend",
18841 		' '
18842 	);
18843 
18844 	function Dispatcher(settings) {
18845 		var self = this, scope, bindings = {}, toggleEvent;
18846 
18847 		function returnFalse() {
18848 			return false;
18849 		}
18850 
18851 		function returnTrue() {
18852 			return true;
18853 		}
18854 
18855 		settings = settings || {};
18856 		scope = settings.scope || self;
18857 		toggleEvent = settings.toggleEvent || returnFalse;
18858 
18859 		/**
18860 		 * Fires the specified event by name.
18861 		 *
18862 		 * @method fire
18863 		 * @param {String} name Name of the event to fire.
18864 		 * @param {Object?} args Event arguments.
18865 		 * @return {Object} Event args instance passed in.
18866 		 * @example
18867 		 * instance.fire('event', {...});
18868 		 */
18869 		function fire(name, args) {
18870 			var handlers, i, l, callback;
18871 
18872 			name = name.toLowerCase();
18873 			args = args || {};
18874 			args.type = name;
18875 
18876 			// Setup target is there isn't one
18877 			if (!args.target) {
18878 				args.target = scope;
18879 			}
18880 
18881 			// Add event delegation methods if they are missing
18882 			if (!args.preventDefault) {
18883 				// Add preventDefault method
18884 				args.preventDefault = function() {
18885 					args.isDefaultPrevented = returnTrue;
18886 				};
18887 
18888 				// Add stopPropagation
18889 				args.stopPropagation = function() {
18890 					args.isPropagationStopped = returnTrue;
18891 				};
18892 
18893 				// Add stopImmediatePropagation
18894 				args.stopImmediatePropagation = function() {
18895 					args.isImmediatePropagationStopped = returnTrue;
18896 				};
18897 
18898 				// Add event delegation states
18899 				args.isDefaultPrevented = returnFalse;
18900 				args.isPropagationStopped = returnFalse;
18901 				args.isImmediatePropagationStopped = returnFalse;
18902 			}
18903 
18904 			if (settings.beforeFire) {
18905 				settings.beforeFire(args);
18906 			}
18907 
18908 			handlers = bindings[name];
18909 			if (handlers) {
18910 				for (i = 0, l = handlers.length; i < l; i++) {
18911 					handlers[i] = callback = handlers[i];
18912 
18913 					// Unbind handlers marked with "once"
18914 					if (callback.once) {
18915 						off(name, callback);
18916 					}
18917 
18918 					// Stop immediate propagation if needed
18919 					if (args.isImmediatePropagationStopped()) {
18920 						args.stopPropagation();
18921 						return args;
18922 					}
18923 
18924 					// If callback returns false then prevent default and stop all propagation
18925 					if (callback.call(scope, args) === false) {
18926 						args.preventDefault();
18927 						return args;
18928 					}
18929 				}
18930 			}
18931 
18932 			return args;
18933 		}
18934 
18935 		/**
18936 		 * Binds an event listener to a specific event by name.
18937 		 *
18938 		 * @method on
18939 		 * @param {String} name Event name or space separated list of events to bind.
18940 		 * @param {callback} callback Callback to be executed when the event occurs.
18941 		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
18942 		 * @return {Object} Current class instance.
18943 		 * @example
18944 		 * instance.on('event', function(e) {
18945 		 *     // Callback logic
18946 		 * });
18947 		 */
18948 		function on(name, callback, prepend) {
18949 			var handlers, names, i;
18950 
18951 			if (callback === false) {
18952 				callback = returnFalse;
18953 			}
18954 
18955 			if (callback) {
18956 				names = name.toLowerCase().split(' ');
18957 				i = names.length;
18958 				while (i--) {
18959 					name = names[i];
18960 					handlers = bindings[name];
18961 					if (!handlers) {
18962 						handlers = bindings[name] = [];
18963 						toggleEvent(name, true);
18964 					}
18965 
18966 					if (prepend) {
18967 						handlers.unshift(callback);
18968 					} else {
18969 						handlers.push(callback);
18970 					}
18971 				}
18972 			}
18973 
18974 			return self;
18975 		}
18976 
18977 		/**
18978 		 * Unbinds an event listener to a specific event by name.
18979 		 *
18980 		 * @method off
18981 		 * @param {String?} name Name of the event to unbind.
18982 		 * @param {callback?} callback Callback to unbind.
18983 		 * @return {Object} Current class instance.
18984 		 * @example
18985 		 * // Unbind specific callback
18986 		 * instance.off('event', handler);
18987 		 *
18988 		 * // Unbind all listeners by name
18989 		 * instance.off('event');
18990 		 *
18991 		 * // Unbind all events
18992 		 * instance.off();
18993 		 */
18994 		function off(name, callback) {
18995 			var i, handlers, bindingName, names, hi;
18996 
18997 			if (name) {
18998 				names = name.toLowerCase().split(' ');
18999 				i = names.length;
19000 				while (i--) {
19001 					name = names[i];
19002 					handlers = bindings[name];
19003 
19004 					// Unbind all handlers
19005 					if (!name) {
19006 						for (bindingName in bindings) {
19007 							toggleEvent(bindingName, false);
19008 							delete bindings[bindingName];
19009 						}
19010 
19011 						return self;
19012 					}
19013 
19014 					if (handlers) {
19015 						// Unbind all by name
19016 						if (!callback) {
19017 							handlers.length = 0;
19018 						} else {
19019 							// Unbind specific ones
19020 							hi = handlers.length;
19021 							while (hi--) {
19022 								if (handlers[hi] === callback) {
19023 									handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
19024 									bindings[name] = handlers;
19025 								}
19026 							}
19027 						}
19028 
19029 						if (!handlers.length) {
19030 							toggleEvent(name, false);
19031 							delete bindings[name];
19032 						}
19033 					}
19034 				}
19035 			} else {
19036 				for (name in bindings) {
19037 					toggleEvent(name, false);
19038 				}
19039 
19040 				bindings = {};
19041 			}
19042 
19043 			return self;
19044 		}
19045 
19046 		/**
19047 		 * Binds an event listener to a specific event by name
19048 		 * and automatically unbind the event once the callback fires.
19049 		 *
19050 		 * @method once
19051 		 * @param {String} name Event name or space separated list of events to bind.
19052 		 * @param {callback} callback Callback to be executed when the event occurs.
19053 		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
19054 		 * @return {Object} Current class instance.
19055 		 * @example
19056 		 * instance.once('event', function(e) {
19057 		 *     // Callback logic
19058 		 * });
19059 		 */
19060 		function once(name, callback, prepend) {
19061 			callback.once = true;
19062 			return on(name, callback, prepend);
19063 		}
19064 
19065 		/**
19066 		 * Returns true/false if the dispatcher has a event of the specified name.
19067 		 *
19068 		 * @method has
19069 		 * @param {String} name Name of the event to check for.
19070 		 * @return {Boolean} true/false if the event exists or not.
19071 		 */
19072 		function has(name) {
19073 			name = name.toLowerCase();
19074 			return !(!bindings[name] || bindings[name].length === 0);
19075 		}
19076 
19077 		// Expose
19078 		self.fire = fire;
19079 		self.on = on;
19080 		self.off = off;
19081 		self.once = once;
19082 		self.has = has;
19083 	}
19084 
19085 	/**
19086 	 * Returns true/false if the specified event name is a native browser event or not.
19087 	 *
19088 	 * @method isNative
19089 	 * @param {String} name Name to check if it's native.
19090 	 * @return {Boolean} true/false if the event is native or not.
19091 	 * @static
19092 	 */
19093 	Dispatcher.isNative = function(name) {
19094 		return !!nativeEvents[name.toLowerCase()];
19095 	};
19096 
19097 	return Dispatcher;
19098 });
19099 
19100 // Included from: js/tinymce/classes/ui/Selector.js
19101 
19102 /**
19103  * Selector.js
19104  *
19105  * Copyright, Moxiecode Systems AB
19106  * Released under LGPL License.
19107  *
19108  * License: http://www.tinymce.com/license
19109  * Contributing: http://www.tinymce.com/contributing
19110  */
19111 
19112 /*eslint no-nested-ternary:0 */
19113 
19114 /**
19115  * Selector engine, enables you to select controls by using CSS like expressions.
19116  * We currently only support basic CSS expressions to reduce the size of the core
19117  * and the ones we support should be enough for most cases.
19118  *
19119  * @example
19120  * Supported expressions:
19121  *  element
19122  *  element#name
19123  *  element.class
19124  *  element[attr]
19125  *  element[attr*=value]
19126  *  element[attr~=value]
19127  *  element[attr!=value]
19128  *  element[attr^=value]
19129  *  element[attr$=value]
19130  *  element:<state>
19131  *  element:not(<expression>)
19132  *  element:first
19133  *  element:last
19134  *  element:odd
19135  *  element:even
19136  *  element element
19137  *  element > element
19138  *
19139  * @class tinymce.ui.Selector
19140  */
19141 define("tinymce/ui/Selector", [
19142 	"tinymce/util/Class"
19143 ], function(Class) {
19144 	"use strict";
19145 
19146 	/**
19147 	 * Produces an array with a unique set of objects. It will not compare the values
19148 	 * but the references of the objects.
19149 	 *
19150 	 * @private
19151 	 * @method unqiue
19152 	 * @param {Array} array Array to make into an array with unique items.
19153 	 * @return {Array} Array with unique items.
19154 	 */
19155 	function unique(array) {
19156 		var uniqueItems = [], i = array.length, item;
19157 
19158 		while (i--) {
19159 			item = array[i];
19160 
19161 			if (!item.__checked) {
19162 				uniqueItems.push(item);
19163 				item.__checked = 1;
19164 			}
19165 		}
19166 
19167 		i = uniqueItems.length;
19168 		while (i--) {
19169 			delete uniqueItems[i].__checked;
19170 		}
19171 
19172 		return uniqueItems;
19173 	}
19174 
19175 	var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
19176 
19177 	/*jshint maxlen:255 */
19178 	/*eslint max-len:0 */
19179 	var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
19180 		whiteSpace = /^\s*|\s*$/g,
19181 		Collection;
19182 
19183 	var Selector = Class.extend({
19184 		/**
19185 		 * Constructs a new Selector instance.
19186 		 *
19187 		 * @constructor
19188 		 * @method init
19189 		 * @param {String} selector CSS like selector expression.
19190 		 */
19191 		init: function(selector) {
19192 			var match = this.match;
19193 
19194 			function compileNameFilter(name) {
19195 				if (name) {
19196 					name = name.toLowerCase();
19197 
19198 					return function(item) {
19199 						return name === '*' || item.type === name;
19200 					};
19201 				}
19202 			}
19203 
19204 			function compileIdFilter(id) {
19205 				if (id) {
19206 					return function(item) {
19207 						return item._name === id;
19208 					};
19209 				}
19210 			}
19211 
19212 			function compileClassesFilter(classes) {
19213 				if (classes) {
19214 					classes = classes.split('.');
19215 
19216 					return function(item) {
19217 						var i = classes.length;
19218 
19219 						while (i--) {
19220 							if (!item.hasClass(classes[i])) {
19221 								return false;
19222 							}
19223 						}
19224 
19225 						return true;
19226 					};
19227 				}
19228 			}
19229 
19230 			function compileAttrFilter(name, cmp, check) {
19231 				if (name) {
19232 					return function(item) {
19233 						var value = item[name] ? item[name]() : '';
19234 
19235 						return !cmp ? !!check :
19236 							cmp === "=" ? value === check :
19237 							cmp === "*=" ? value.indexOf(check) >= 0 :
19238 							cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
19239 							cmp === "!=" ? value != check :
19240 							cmp === "^=" ? value.indexOf(check) === 0 :
19241 							cmp === "$=" ? value.substr(value.length - check.length) === check :
19242 							false;
19243 					};
19244 				}
19245 			}
19246 
19247 			function compilePsuedoFilter(name) {
19248 				var notSelectors;
19249 
19250 				if (name) {
19251 					name = /(?:not\((.+)\))|(.+)/i.exec(name);
19252 
19253 					if (!name[1]) {
19254 						name = name[2];
19255 
19256 						return function(item, index, length) {
19257 							return name === 'first' ? index === 0 :
19258 								name === 'last' ? index === length - 1 :
19259 								name === 'even' ? index % 2 === 0 :
19260 								name === 'odd' ? index % 2 === 1 :
19261 								item[name] ? item[name]() :
19262 								false;
19263 						};
19264 					} else {
19265 						// Compile not expression
19266 						notSelectors = parseChunks(name[1], []);
19267 
19268 						return function(item) {
19269 							return !match(item, notSelectors);
19270 						};
19271 					}
19272 				}
19273 			}
19274 
19275 			function compile(selector, filters, direct) {
19276 				var parts;
19277 
19278 				function add(filter) {
19279 					if (filter) {
19280 						filters.push(filter);
19281 					}
19282 				}
19283 
19284 				// Parse expression into parts
19285 				parts = expression.exec(selector.replace(whiteSpace, ''));
19286 
19287 				add(compileNameFilter(parts[1]));
19288 				add(compileIdFilter(parts[2]));
19289 				add(compileClassesFilter(parts[3]));
19290 				add(compileAttrFilter(parts[4], parts[5], parts[6]));
19291 				add(compilePsuedoFilter(parts[7]));
19292 
19293 				// Mark the filter with psuedo for performance
19294 				filters.psuedo = !!parts[7];
19295 				filters.direct = direct;
19296 
19297 				return filters;
19298 			}
19299 
19300 			// Parser logic based on Sizzle by John Resig
19301 			function parseChunks(selector, selectors) {
19302 				var parts = [], extra, matches, i;
19303 
19304 				do {
19305 					chunker.exec("");
19306 					matches = chunker.exec(selector);
19307 
19308 					if (matches) {
19309 						selector = matches[3];
19310 						parts.push(matches[1]);
19311 
19312 						if (matches[2]) {
19313 							extra = matches[3];
19314 							break;
19315 						}
19316 					}
19317 				} while (matches);
19318 
19319 				if (extra) {
19320 					parseChunks(extra, selectors);
19321 				}
19322 
19323 				selector = [];
19324 				for (i = 0; i < parts.length; i++) {
19325 					if (parts[i] != '>') {
19326 						selector.push(compile(parts[i], [], parts[i - 1] === '>'));
19327 					}
19328 				}
19329 
19330 				selectors.push(selector);
19331 
19332 				return selectors;
19333 			}
19334 
19335 			this._selectors = parseChunks(selector, []);
19336 		},
19337 
19338 		/**
19339 		 * Returns true/false if the selector matches the specified control.
19340 		 *
19341 		 * @method match
19342 		 * @param {tinymce.ui.Control} control Control to match agains the selector.
19343 		 * @param {Array} selectors Optional array of selectors, mostly used internally.
19344 		 * @return {Boolean} true/false state if the control matches or not.
19345 		 */
19346 		match: function(control, selectors) {
19347 			var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
19348 
19349 			selectors = selectors || this._selectors;
19350 			for (i = 0, l = selectors.length; i < l; i++) {
19351 				selector = selectors[i];
19352 				sl = selector.length;
19353 				item = control;
19354 				count = 0;
19355 
19356 				for (si = sl - 1; si >= 0; si--) {
19357 					filters = selector[si];
19358 
19359 					while (item) {
19360 						// Find the index and length since a psuedo filter like :first needs it
19361 						if (filters.psuedo) {
19362 							siblings = item.parent().items();
19363 							index = length = siblings.length;
19364 							while (index--) {
19365 								if (siblings[index] === item) {
19366 									break;
19367 								}
19368 							}
19369 						}
19370 
19371 						for (fi = 0, fl = filters.length; fi < fl; fi++) {
19372 							if (!filters[fi](item, index, length)) {
19373 								fi = fl + 1;
19374 								break;
19375 							}
19376 						}
19377 
19378 						if (fi === fl) {
19379 							count++;
19380 							break;
19381 						} else {
19382 							// If it didn't match the right most expression then
19383 							// break since it's no point looking at the parents
19384 							if (si === sl - 1) {
19385 								break;
19386 							}
19387 						}
19388 
19389 						item = item.parent();
19390 					}
19391 				}
19392 
19393 				// If we found all selectors then return true otherwise continue looking
19394 				if (count === sl) {
19395 					return true;
19396 				}
19397 			}
19398 
19399 			return false;
19400 		},
19401 
19402 		/**
19403 		 * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
19404 		 *
19405 		 * @method find
19406 		 * @param {tinymce.ui.Control} container Container to look for items in.
19407 		 * @return {tinymce.ui.Collection} Collection with matched elements.
19408 		 */
19409 		find: function(container) {
19410 			var matches = [], i, l, selectors = this._selectors;
19411 
19412 			function collect(items, selector, index) {
19413 				var i, l, fi, fl, item, filters = selector[index];
19414 
19415 				for (i = 0, l = items.length; i < l; i++) {
19416 					item = items[i];
19417 
19418 					// Run each filter agains the item
19419 					for (fi = 0, fl = filters.length; fi < fl; fi++) {
19420 						if (!filters[fi](item, i, l)) {
19421 							fi = fl + 1;
19422 							break;
19423 						}
19424 					}
19425 
19426 					// All filters matched the item
19427 					if (fi === fl) {
19428 						// Matched item is on the last expression like: panel toolbar [button]
19429 						if (index == selector.length - 1) {
19430 							matches.push(item);
19431 						} else {
19432 							// Collect next expression type
19433 							if (item.items) {
19434 								collect(item.items(), selector, index + 1);
19435 							}
19436 						}
19437 					} else if (filters.direct) {
19438 						return;
19439 					}
19440 
19441 					// Collect child items
19442 					if (item.items) {
19443 						collect(item.items(), selector, index);
19444 					}
19445 				}
19446 			}
19447 
19448 			if (container.items) {
19449 				for (i = 0, l = selectors.length; i < l; i++) {
19450 					collect(container.items(), selectors[i], 0);
19451 				}
19452 
19453 				// Unique the matches if needed
19454 				if (l > 1) {
19455 					matches = unique(matches);
19456 				}
19457 			}
19458 
19459 			// Fix for circular reference
19460 			if (!Collection) {
19461 				// TODO: Fix me!
19462 				Collection = Selector.Collection;
19463 			}
19464 
19465 			return new Collection(matches);
19466 		}
19467 	});
19468 
19469 	return Selector;
19470 });
19471 
19472 // Included from: js/tinymce/classes/ui/Collection.js
19473 
19474 /**
19475  * Collection.js
19476  *
19477  * Copyright, Moxiecode Systems AB
19478  * Released under LGPL License.
19479  *
19480  * License: http://www.tinymce.com/license
19481  * Contributing: http://www.tinymce.com/contributing
19482  */
19483 
19484 /**
19485  * Control collection, this class contains control instances and it enables you to
19486  * perform actions on all the contained items. This is very similar to how jQuery works.
19487  *
19488  * @example
19489  * someCollection.show().disabled(true);
19490  *
19491  * @class tinymce.ui.Collection
19492  */
19493 define("tinymce/ui/Collection", [
19494 	"tinymce/util/Tools",
19495 	"tinymce/ui/Selector",
19496 	"tinymce/util/Class"
19497 ], function(Tools, Selector, Class) {
19498 	"use strict";
19499 
19500 	var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
19501 
19502 	proto = {
19503 		/**
19504 		 * Current number of contained control instances.
19505 		 *
19506 		 * @field length
19507 		 * @type Number
19508 		 */
19509 		length: 0,
19510 
19511 		/**
19512 		 * Constructor for the collection.
19513 		 *
19514 		 * @constructor
19515 		 * @method init
19516 		 * @param {Array} items Optional array with items to add.
19517 		 */
19518 		init: function(items) {
19519 			if (items) {
19520 				this.add(items);
19521 			}
19522 		},
19523 
19524 		/**
19525 		 * Adds new items to the control collection.
19526 		 *
19527 		 * @method add
19528 		 * @param {Array} items Array if items to add to collection.
19529 		 * @return {tinymce.ui.Collection} Current collection instance.
19530 		 */
19531 		add: function(items) {
19532 			var self = this;
19533 
19534 			// Force single item into array
19535 			if (!Tools.isArray(items)) {
19536 				if (items instanceof Collection) {
19537 					self.add(items.toArray());
19538 				} else {
19539 					push.call(self, items);
19540 				}
19541 			} else {
19542 				push.apply(self, items);
19543 			}
19544 
19545 			return self;
19546 		},
19547 
19548 		/**
19549 		 * Sets the contents of the collection. This will remove any existing items
19550 		 * and replace them with the ones specified in the input array.
19551 		 *
19552 		 * @method set
19553 		 * @param {Array} items Array with items to set into the Collection.
19554 		 * @return {tinymce.ui.Collection} Collection instance.
19555 		 */
19556 		set: function(items) {
19557 			var self = this, len = self.length, i;
19558 
19559 			self.length = 0;
19560 			self.add(items);
19561 
19562 			// Remove old entries
19563 			for (i = self.length; i < len; i++) {
19564 				delete self[i];
19565 			}
19566 
19567 			return self;
19568 		},
19569 
19570 		/**
19571 		 * Filters the collection item based on the specified selector expression or selector function.
19572 		 *
19573 		 * @method filter
19574 		 * @param {String} selector Selector expression to filter items by.
19575 		 * @return {tinymce.ui.Collection} Collection containing the filtered items.
19576 		 */
19577 		filter: function(selector) {
19578 			var self = this, i, l, matches = [], item, match;
19579 
19580 			// Compile string into selector expression
19581 			if (typeof(selector) === "string") {
19582 				selector = new Selector(selector);
19583 
19584 				match = function(item) {
19585 					return selector.match(item);
19586 				};
19587 			} else {
19588 				// Use selector as matching function
19589 				match = selector;
19590 			}
19591 
19592 			for (i = 0, l = self.length; i < l; i++) {
19593 				item = self[i];
19594 
19595 				if (match(item)) {
19596 					matches.push(item);
19597 				}
19598 			}
19599 
19600 			return new Collection(matches);
19601 		},
19602 
19603 		/**
19604 		 * Slices the items within the collection.
19605 		 *
19606 		 * @method slice
19607 		 * @param {Number} index Index to slice at.
19608 		 * @param {Number} len Optional length to slice.
19609 		 * @return {tinymce.ui.Collection} Current collection.
19610 		 */
19611 		slice: function() {
19612 			return new Collection(slice.apply(this, arguments));
19613 		},
19614 
19615 		/**
19616 		 * Makes the current collection equal to the specified index.
19617 		 *
19618 		 * @method eq
19619 		 * @param {Number} index Index of the item to set the collection to.
19620 		 * @return {tinymce.ui.Collection} Current collection.
19621 		 */
19622 		eq: function(index) {
19623 			return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
19624 		},
19625 
19626 		/**
19627 		 * Executes the specified callback on each item in collection.
19628 		 *
19629 		 * @method each
19630 		 * @param {function} callback Callback to execute for each item in collection.
19631 		 * @return {tinymce.ui.Collection} Current collection instance.
19632 		 */
19633 		each: function(callback) {
19634 			Tools.each(this, callback);
19635 
19636 			return this;
19637 		},
19638 
19639 		/**
19640 		 * Returns an JavaScript array object of the contents inside the collection.
19641 		 *
19642 		 * @method toArray
19643 		 * @return {Array} Array with all items from collection.
19644 		 */
19645 		toArray: function() {
19646 			return Tools.toArray(this);
19647 		},
19648 
19649 		/**
19650 		 * Finds the index of the specified control or return -1 if it isn't in the collection.
19651 		 *
19652 		 * @method indexOf
19653 		 * @param {Control} ctrl Control instance to look for.
19654 		 * @return {Number} Index of the specified control or -1.
19655 		 */
19656 		indexOf: function(ctrl) {
19657 			var self = this, i = self.length;
19658 
19659 			while (i--) {
19660 				if (self[i] === ctrl) {
19661 					break;
19662 				}
19663 			}
19664 
19665 			return i;
19666 		},
19667 
19668 		/**
19669 		 * Returns a new collection of the contents in reverse order.
19670 		 *
19671 		 * @method reverse
19672 		 * @return {tinymce.ui.Collection} Collection instance with reversed items.
19673 		 */
19674 		reverse: function() {
19675 			return new Collection(Tools.toArray(this).reverse());
19676 		},
19677 
19678 		/**
19679 		 * Returns true/false if the class exists or not.
19680 		 *
19681 		 * @method hasClass
19682 		 * @param {String} cls Class to check for.
19683 		 * @return {Boolean} true/false state if the class exists or not.
19684 		 */
19685 		hasClass: function(cls) {
19686 			return this[0] ? this[0].hasClass(cls) : false;
19687 		},
19688 
19689 		/**
19690 		 * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
19691 		 *
19692 		 * @method prop
19693 		 * @param {String} name Property name to get/set.
19694 		 * @param {Object} value Optional object value to set.
19695 		 * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
19696 		 */
19697 		prop: function(name, value) {
19698 			var self = this, undef, item;
19699 
19700 			if (value !== undef) {
19701 				self.each(function(item) {
19702 					if (item[name]) {
19703 						item[name](value);
19704 					}
19705 				});
19706 
19707 				return self;
19708 			}
19709 
19710 			item = self[0];
19711 
19712 			if (item && item[name]) {
19713 				return item[name]();
19714 			}
19715 		},
19716 
19717 		/**
19718 		 * Executes the specific function name with optional arguments an all items in collection if it exists.
19719 		 *
19720 		 * @example collection.exec("myMethod", arg1, arg2, arg3);
19721 		 * @method exec
19722 		 * @param {String} name Name of the function to execute.
19723 		 * @param {Object} ... Multiple arguments to pass to each function.
19724 		 * @return {tinymce.ui.Collection} Current collection.
19725 		 */
19726 		exec: function(name) {
19727 			var self = this, args = Tools.toArray(arguments).slice(1);
19728 
19729 			self.each(function(item) {
19730 				if (item[name]) {
19731 					item[name].apply(item, args);
19732 				}
19733 			});
19734 
19735 			return self;
19736 		},
19737 
19738 		/**
19739 		 * Remove all items from collection and DOM.
19740 		 *
19741 		 * @method remove
19742 		 * @return {tinymce.ui.Collection} Current collection.
19743 		 */
19744 		remove: function() {
19745 			var i = this.length;
19746 
19747 			while (i--) {
19748 				this[i].remove();
19749 			}
19750 
19751 			return this;
19752 		}
19753 
19754 		/**
19755 		 * Fires the specified event by name and arguments on the control. This will execute all
19756 		 * bound event handlers.
19757 		 *
19758 		 * @method fire
19759 		 * @param {String} name Name of the event to fire.
19760 		 * @param {Object} args Optional arguments to pass to the event.
19761 		 * @return {tinymce.ui.Collection} Current collection instance.
19762 		 */
19763 		// fire: function(event, args) {}, -- Generated by code below
19764 
19765 		/**
19766 		 * Binds a callback to the specified event. This event can both be
19767 		 * native browser events like "click" or custom ones like PostRender.
19768 		 *
19769 		 * The callback function will have two parameters the first one being the control that received the event
19770 		 * the second one will be the event object either the browsers native event object or a custom JS object.
19771 		 *
19772 		 * @method on
19773 		 * @param {String} name Name of the event to bind. For example "click".
19774 		 * @param {String/function} callback Callback function to execute ones the event occurs.
19775 		 * @return {tinymce.ui.Collection} Current collection instance.
19776 		 */
19777 		// on: function(name, callback) {}, -- Generated by code below
19778 
19779 		/**
19780 		 * Unbinds the specified event and optionally a specific callback. If you omit the name
19781 		 * parameter all event handlers will be removed. If you omit the callback all event handles
19782 		 * by the specified name will be removed.
19783 		 *
19784 		 * @method off
19785 		 * @param {String} name Optional name for the event to unbind.
19786 		 * @param {function} callback Optional callback function to unbind.
19787 		 * @return {tinymce.ui.Collection} Current collection instance.
19788 		 */
19789 		// off: function(name, callback) {}, -- Generated by code below
19790 
19791 		/**
19792 		 * Shows the items in the current collection.
19793 		 *
19794 		 * @method show
19795 		 * @return {tinymce.ui.Collection} Current collection instance.
19796 		 */
19797 		// show: function() {}, -- Generated by code below
19798 
19799 		/**
19800 		 * Hides the items in the current collection.
19801 		 *
19802 		 * @method hide
19803 		 * @return {tinymce.ui.Collection} Current collection instance.
19804 		 */
19805 		// hide: function() {}, -- Generated by code below
19806 
19807 		/**
19808 		 * Sets/gets the text contents of the items in the current collection.
19809 		 *
19810 		 * @method text
19811 		 * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
19812 		 */
19813 		// text: function(value) {}, -- Generated by code below
19814 
19815 		/**
19816 		 * Sets/gets the name contents of the items in the current collection.
19817 		 *
19818 		 * @method name
19819 		 * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
19820 		 */
19821 		// name: function(value) {}, -- Generated by code below
19822 
19823 		/**
19824 		 * Sets/gets the disabled state on the items in the current collection.
19825 		 *
19826 		 * @method disabled
19827 		 * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
19828 		 */
19829 		// disabled: function(state) {}, -- Generated by code below
19830 
19831 		/**
19832 		 * Sets/gets the active state on the items in the current collection.
19833 		 *
19834 		 * @method active
19835 		 * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
19836 		 */
19837 		// active: function(state) {}, -- Generated by code below
19838 
19839 		/**
19840 		 * Sets/gets the selected state on the items in the current collection.
19841 		 *
19842 		 * @method selected
19843 		 * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
19844 		 */
19845 		// selected: function(state) {}, -- Generated by code below
19846 
19847 		/**
19848 		 * Sets/gets the selected state on the items in the current collection.
19849 		 *
19850 		 * @method visible
19851 		 * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
19852 		 */
19853 		// visible: function(state) {}, -- Generated by code below
19854 
19855 		/**
19856 		 * Adds a class to all items in the collection.
19857 		 *
19858 		 * @method addClass
19859 		 * @param {String} cls Class to add to each item.
19860 		 * @return {tinymce.ui.Collection} Current collection instance.
19861 		 */
19862 		// addClass: function(cls) {}, -- Generated by code below
19863 
19864 		/**
19865 		 * Removes the specified class from all items in collection.
19866 		 *
19867 		 * @method removeClass
19868 		 * @param {String} cls Class to remove from each item.
19869 		 * @return {tinymce.ui.Collection} Current collection instance.
19870 		 */
19871 		// removeClass: function(cls) {}, -- Generated by code below
19872 	};
19873 
19874 	// Extend tinymce.ui.Collection prototype with some generated control specific methods
19875 	Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
19876 		proto[name] = function() {
19877 			var args = Tools.toArray(arguments);
19878 
19879 			this.each(function(ctrl) {
19880 				if (name in ctrl) {
19881 					ctrl[name].apply(ctrl, args);
19882 				}
19883 			});
19884 
19885 			return this;
19886 		};
19887 	});
19888 
19889 	// Extend tinymce.ui.Collection prototype with some property methods
19890 	Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
19891 		proto[name] = function(value) {
19892 			return this.prop(name, value);
19893 		};
19894 	});
19895 
19896 	// Create class based on the new prototype
19897 	Collection = Class.extend(proto);
19898 
19899 	// Stick Collection into Selector to prevent circual references
19900 	Selector.Collection = Collection;
19901 
19902 	return Collection;
19903 });
19904 
19905 // Included from: js/tinymce/classes/ui/DomUtils.js
19906 
19907 /**
19908  * DOMUtils.js
19909  *
19910  * Copyright, Moxiecode Systems AB
19911  * Released under LGPL License.
19912  *
19913  * License: http://www.tinymce.com/license
19914  * Contributing: http://www.tinymce.com/contributing
19915  */
19916 
19917 define("tinymce/ui/DomUtils", [
19918 	"tinymce/util/Tools",
19919 	"tinymce/dom/DOMUtils"
19920 ], function(Tools, DOMUtils) {
19921 	"use strict";
19922 
19923 	var count = 0;
19924 
19925 	return {
19926 		id: function() {
19927 			return 'mceu_' + (count++);
19928 		},
19929 
19930 		createFragment: function(html) {
19931 			return DOMUtils.DOM.createFragment(html);
19932 		},
19933 
19934 		getWindowSize: function() {
19935 			return DOMUtils.DOM.getViewPort();
19936 		},
19937 
19938 		getSize: function(elm) {
19939 			var width, height;
19940 
19941 			if (elm.getBoundingClientRect) {
19942 				var rect = elm.getBoundingClientRect();
19943 
19944 				width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
19945 				height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
19946 			} else {
19947 				width = elm.offsetWidth;
19948 				height = elm.offsetHeight;
19949 			}
19950 
19951 			return {width: width, height: height};
19952 		},
19953 
19954 		getPos: function(elm, root) {
19955 			return DOMUtils.DOM.getPos(elm, root);
19956 		},
19957 
19958 		getViewPort: function(win) {
19959 			return DOMUtils.DOM.getViewPort(win);
19960 		},
19961 
19962 		get: function(id) {
19963 			return document.getElementById(id);
19964 		},
19965 
19966 		addClass : function(elm, cls) {
19967 			return DOMUtils.DOM.addClass(elm, cls);
19968 		},
19969 
19970 		removeClass : function(elm, cls) {
19971 			return DOMUtils.DOM.removeClass(elm, cls);
19972 		},
19973 
19974 		hasClass : function(elm, cls) {
19975 			return DOMUtils.DOM.hasClass(elm, cls);
19976 		},
19977 
19978 		toggleClass: function(elm, cls, state) {
19979 			return DOMUtils.DOM.toggleClass(elm, cls, state);
19980 		},
19981 
19982 		css: function(elm, name, value) {
19983 			return DOMUtils.DOM.setStyle(elm, name, value);
19984 		},
19985 
19986 		getRuntimeStyle: function(elm, name) {
19987 			return DOMUtils.DOM.getStyle(elm, name, true);
19988 		},
19989 
19990 		on: function(target, name, callback, scope) {
19991 			return DOMUtils.DOM.bind(target, name, callback, scope);
19992 		},
19993 
19994 		off: function(target, name, callback) {
19995 			return DOMUtils.DOM.unbind(target, name, callback);
19996 		},
19997 
19998 		fire: function(target, name, args) {
19999 			return DOMUtils.DOM.fire(target, name, args);
20000 		},
20001 
20002 		innerHtml: function(elm, html) {
20003 			// Workaround for <div> in <p> bug on IE 8 #6178
20004 			DOMUtils.DOM.setHTML(elm, html);
20005 		}
20006 	};
20007 });
20008 
20009 // Included from: js/tinymce/classes/ui/Control.js
20010 
20011 /**
20012  * Control.js
20013  *
20014  * Copyright, Moxiecode Systems AB
20015  * Released under LGPL License.
20016  *
20017  * License: http://www.tinymce.com/license
20018  * Contributing: http://www.tinymce.com/contributing
20019  */
20020 
20021 /*eslint consistent-this:0 */
20022 
20023 /**
20024  * This is the base class for all controls and containers. All UI control instances inherit
20025  * from this one as it has the base logic needed by all of them.
20026  *
20027  * @class tinymce.ui.Control
20028  */
20029 define("tinymce/ui/Control", [
20030 	"tinymce/util/Class",
20031 	"tinymce/util/Tools",
20032 	"tinymce/util/EventDispatcher",
20033 	"tinymce/ui/Collection",
20034 	"tinymce/ui/DomUtils"
20035 ], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
20036 	"use strict";
20037 
20038 	var hasMouseWheelEventSupport = "onmousewheel" in document;
20039 	var hasWheelEventSupport = false;
20040 	var classPrefix = "mce-";
20041 
20042 	function getEventDispatcher(obj) {
20043 		if (!obj._eventDispatcher) {
20044 			obj._eventDispatcher = new EventDispatcher({
20045 				scope: obj,
20046 				toggleEvent: function(name, state) {
20047 					if (state && EventDispatcher.isNative(name)) {
20048 						if (!obj._nativeEvents) {
20049 							obj._nativeEvents = {};
20050 						}
20051 
20052 						obj._nativeEvents[name] = true;
20053 
20054 						if (obj._rendered) {
20055 							obj.bindPendingEvents();
20056 						}
20057 					}
20058 				}
20059 			});
20060 		}
20061 
20062 		return obj._eventDispatcher;
20063 	}
20064 
20065 	var Control = Class.extend({
20066 		Statics: {
20067 			classPrefix: classPrefix
20068 		},
20069 
20070 		isRtl: function() {
20071 			return Control.rtl;
20072 		},
20073 
20074 		/**
20075 		 * Class/id prefix to use for all controls.
20076 		 *
20077 		 * @final
20078 		 * @field {String} classPrefix
20079 		 */
20080 		classPrefix: classPrefix,
20081 
20082 		/**
20083 		 * Constructs a new control instance with the specified settings.
20084 		 *
20085 		 * @constructor
20086 		 * @param {Object} settings Name/value object with settings.
20087 		 * @setting {String} style Style CSS properties to add.
20088 		 * @setting {String} border Border box values example: 1 1 1 1
20089 		 * @setting {String} padding Padding box values example: 1 1 1 1
20090 		 * @setting {String} margin Margin box values example: 1 1 1 1
20091 		 * @setting {Number} minWidth Minimal width for the control.
20092 		 * @setting {Number} minHeight Minimal height for the control.
20093 		 * @setting {String} classes Space separated list of classes to add.
20094 		 * @setting {String} role WAI-ARIA role to use for control.
20095 		 * @setting {Boolean} hidden Is the control hidden by default.
20096 		 * @setting {Boolean} disabled Is the control disabled by default.
20097 		 * @setting {String} name Name of the control instance.
20098 		 */
20099 		init: function(settings) {
20100 			var self = this, classes, i;
20101 
20102 			self.settings = settings = Tools.extend({}, self.Defaults, settings);
20103 
20104 			// Initial states
20105 			self._id = settings.id || DomUtils.id();
20106 			self._text = self._name = '';
20107 			self._width = self._height = 0;
20108 			self._aria = {role: settings.role};
20109 			this._elmCache = {};
20110 
20111 			// Setup classes
20112 			classes = settings.classes;
20113 			if (classes) {
20114 				classes = classes.split(' ');
20115 				classes.map = {};
20116 				i = classes.length;
20117 				while (i--) {
20118 					classes.map[classes[i]] = true;
20119 				}
20120 			}
20121 
20122 			self._classes = classes || [];
20123 			self.visible(true);
20124 
20125 			// Set some properties
20126 			Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
20127 				var value = settings[name], undef;
20128 
20129 				if (value !== undef) {
20130 					self[name](value);
20131 				} else if (self['_' + name] === undef) {
20132 					self['_' + name] = false;
20133 				}
20134 			});
20135 
20136 			self.on('click', function() {
20137 				if (self.disabled()) {
20138 					return false;
20139 				}
20140 			});
20141 
20142 			// TODO: Is this needed duplicate code see above?
20143 			if (settings.classes) {
20144 				Tools.each(settings.classes.split(' '), function(cls) {
20145 					self.addClass(cls);
20146 				});
20147 			}
20148 
20149 			/**
20150 			 * Name/value object with settings for the current control.
20151 			 *
20152 			 * @field {Object} settings
20153 			 */
20154 			self.settings = settings;
20155 
20156 			self._borderBox = self.parseBox(settings.border);
20157 			self._paddingBox = self.parseBox(settings.padding);
20158 			self._marginBox = self.parseBox(settings.margin);
20159 
20160 			if (settings.hidden) {
20161 				self.hide();
20162 			}
20163 		},
20164 
20165 		// Will generate getter/setter methods for these properties
20166 		Properties: 'parent,title,text,width,height,disabled,active,name,value',
20167 
20168 		// Will generate empty dummy functions for these
20169 		Methods: 'renderHtml',
20170 
20171 		/**
20172 		 * Returns the root element to render controls into.
20173 		 *
20174 		 * @method getContainerElm
20175 		 * @return {Element} HTML DOM element to render into.
20176 		 */
20177 		getContainerElm: function() {
20178 			return document.body;
20179 		},
20180 
20181 		/**
20182 		 * Returns a control instance for the current DOM element.
20183 		 *
20184 		 * @method getParentCtrl
20185 		 * @param {Element} elm HTML dom element to get parent control from.
20186 		 * @return {tinymce.ui.Control} Control instance or undefined.
20187 		 */
20188 		getParentCtrl: function(elm) {
20189 			var ctrl, lookup = this.getRoot().controlIdLookup;
20190 
20191 			while (elm && lookup) {
20192 				ctrl = lookup[elm.id];
20193 				if (ctrl) {
20194 					break;
20195 				}
20196 
20197 				elm = elm.parentNode;
20198 			}
20199 
20200 			return ctrl;
20201 		},
20202 
20203 		/**
20204 		 * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
20205 		 *
20206 		 * @method parseBox
20207 		 * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
20208 		 * @return {Object} Object with top/right/bottom/left properties.
20209 		 * @private
20210 		 */
20211 		parseBox: function(value) {
20212 			var len, radix = 10;
20213 
20214 			if (!value) {
20215 				return;
20216 			}
20217 
20218 			if (typeof(value) === "number") {
20219 				value = value || 0;
20220 
20221 				return {
20222 					top: value,
20223 					left: value,
20224 					bottom: value,
20225 					right: value
20226 				};
20227 			}
20228 
20229 			value = value.split(' ');
20230 			len = value.length;
20231 
20232 			if (len === 1) {
20233 				value[1] = value[2] = value[3] = value[0];
20234 			} else if (len === 2) {
20235 				value[2] = value[0];
20236 				value[3] = value[1];
20237 			} else if (len === 3) {
20238 				value[3] = value[1];
20239 			}
20240 
20241 			return {
20242 				top: parseInt(value[0], radix) || 0,
20243 				right: parseInt(value[1], radix) || 0,
20244 				bottom: parseInt(value[2], radix) || 0,
20245 				left: parseInt(value[3], radix) || 0
20246 			};
20247 		},
20248 
20249 		borderBox: function() {
20250 			return this._borderBox;
20251 		},
20252 
20253 		paddingBox: function() {
20254 			return this._paddingBox;
20255 		},
20256 
20257 		marginBox: function() {
20258 			return this._marginBox;
20259 		},
20260 
20261 		measureBox: function(elm, prefix) {
20262 			function getStyle(name) {
20263 				var defaultView = document.defaultView;
20264 
20265 				if (defaultView) {
20266 					// Remove camelcase
20267 					name = name.replace(/[A-Z]/g, function(a) {
20268 						return '-' + a;
20269 					});
20270 
20271 					return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
20272 				}
20273 
20274 				return elm.currentStyle[name];
20275 			}
20276 
20277 			function getSide(name) {
20278 				var val = parseFloat(getStyle(name), 10);
20279 
20280 				return isNaN(val) ? 0 : val;
20281 			}
20282 
20283 			return {
20284 				top: getSide(prefix + "TopWidth"),
20285 				right: getSide(prefix + "RightWidth"),
20286 				bottom: getSide(prefix + "BottomWidth"),
20287 				left: getSide(prefix + "LeftWidth")
20288 			};
20289 		},
20290 
20291 		/**
20292 		 * Initializes the current controls layout rect.
20293 		 * This will be executed by the layout managers to determine the
20294 		 * default minWidth/minHeight etc.
20295 		 *
20296 		 * @method initLayoutRect
20297 		 * @return {Object} Layout rect instance.
20298 		 */
20299 		initLayoutRect: function() {
20300 			var self = this, settings = self.settings, borderBox, layoutRect;
20301 			var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
20302 			var startMinWidth, startMinHeight, initialSize;
20303 
20304 			// Measure the current element
20305 			borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
20306 			self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
20307 			self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
20308 			initialSize = DomUtils.getSize(elm);
20309 
20310 			// Setup minWidth/minHeight and width/height
20311 			startMinWidth = settings.minWidth;
20312 			startMinHeight = settings.minHeight;
20313 			minWidth = startMinWidth || initialSize.width;
20314 			minHeight = startMinHeight || initialSize.height;
20315 			width = settings.width;
20316 			height = settings.height;
20317 			autoResize = settings.autoResize;
20318 			autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
20319 
20320 			width = width || minWidth;
20321 			height = height || minHeight;
20322 
20323 			var deltaW = borderBox.left + borderBox.right;
20324 			var deltaH = borderBox.top + borderBox.bottom;
20325 
20326 			var maxW = settings.maxWidth || 0xFFFF;
20327 			var maxH = settings.maxHeight || 0xFFFF;
20328 
20329 			// Setup initial layout rect
20330 			self._layoutRect = layoutRect = {
20331 				x: settings.x || 0,
20332 				y: settings.y || 0,
20333 				w: width,
20334 				h: height,
20335 				deltaW: deltaW,
20336 				deltaH: deltaH,
20337 				contentW: width - deltaW,
20338 				contentH: height - deltaH,
20339 				innerW: width - deltaW,
20340 				innerH: height - deltaH,
20341 				startMinWidth: startMinWidth || 0,
20342 				startMinHeight: startMinHeight || 0,
20343 				minW: Math.min(minWidth, maxW),
20344 				minH: Math.min(minHeight, maxH),
20345 				maxW: maxW,
20346 				maxH: maxH,
20347 				autoResize: autoResize,
20348 				scrollW: 0
20349 			};
20350 
20351 			self._lastLayoutRect = {};
20352 
20353 			return layoutRect;
20354 		},
20355 
20356 		/**
20357 		 * Getter/setter for the current layout rect.
20358 		 *
20359 		 * @method layoutRect
20360 		 * @param {Object} [newRect] Optional new layout rect.
20361 		 * @return {tinymce.ui.Control/Object} Current control or rect object.
20362 		 */
20363 		layoutRect: function(newRect) {
20364 			var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
20365 
20366 			// Initialize default layout rect
20367 			if (!curRect) {
20368 				curRect = self.initLayoutRect();
20369 			}
20370 
20371 			// Set new rect values
20372 			if (newRect) {
20373 				// Calc deltas between inner and outer sizes
20374 				deltaWidth = curRect.deltaW;
20375 				deltaHeight = curRect.deltaH;
20376 
20377 				// Set x position
20378 				if (newRect.x !== undef) {
20379 					curRect.x = newRect.x;
20380 				}
20381 
20382 				// Set y position
20383 				if (newRect.y !== undef) {
20384 					curRect.y = newRect.y;
20385 				}
20386 
20387 				// Set minW
20388 				if (newRect.minW !== undef) {
20389 					curRect.minW = newRect.minW;
20390 				}
20391 
20392 				// Set minH
20393 				if (newRect.minH !== undef) {
20394 					curRect.minH = newRect.minH;
20395 				}
20396 
20397 				// Set new width and calculate inner width
20398 				size = newRect.w;
20399 				if (size !== undef) {
20400 					size = size < curRect.minW ? curRect.minW : size;
20401 					size = size > curRect.maxW ? curRect.maxW : size;
20402 					curRect.w = size;
20403 					curRect.innerW = size - deltaWidth;
20404 				}
20405 
20406 				// Set new height and calculate inner height
20407 				size = newRect.h;
20408 				if (size !== undef) {
20409 					size = size < curRect.minH ? curRect.minH : size;
20410 					size = size > curRect.maxH ? curRect.maxH : size;
20411 					curRect.h = size;
20412 					curRect.innerH = size - deltaHeight;
20413 				}
20414 
20415 				// Set new inner width and calculate width
20416 				size = newRect.innerW;
20417 				if (size !== undef) {
20418 					size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
20419 					size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
20420 					curRect.innerW = size;
20421 					curRect.w = size + deltaWidth;
20422 				}
20423 
20424 				// Set new height and calculate inner height
20425 				size = newRect.innerH;
20426 				if (size !== undef) {
20427 					size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
20428 					size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
20429 					curRect.innerH = size;
20430 					curRect.h = size + deltaHeight;
20431 				}
20432 
20433 				// Set new contentW
20434 				if (newRect.contentW !== undef) {
20435 					curRect.contentW = newRect.contentW;
20436 				}
20437 
20438 				// Set new contentH
20439 				if (newRect.contentH !== undef) {
20440 					curRect.contentH = newRect.contentH;
20441 				}
20442 
20443 				// Compare last layout rect with the current one to see if we need to repaint or not
20444 				lastLayoutRect = self._lastLayoutRect;
20445 				if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
20446 					lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
20447 					repaintControls = Control.repaintControls;
20448 
20449 					if (repaintControls) {
20450 						if (repaintControls.map && !repaintControls.map[self._id]) {
20451 							repaintControls.push(self);
20452 							repaintControls.map[self._id] = true;
20453 						}
20454 					}
20455 
20456 					lastLayoutRect.x = curRect.x;
20457 					lastLayoutRect.y = curRect.y;
20458 					lastLayoutRect.w = curRect.w;
20459 					lastLayoutRect.h = curRect.h;
20460 				}
20461 
20462 				return self;
20463 			}
20464 
20465 			return curRect;
20466 		},
20467 
20468 		/**
20469 		 * Repaints the control after a layout operation.
20470 		 *
20471 		 * @method repaint
20472 		 */
20473 		repaint: function() {
20474 			var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
20475 
20476 			// Use Math.round on all values on IE < 9
20477 			round = !document.createRange ? Math.round : function(value) {
20478 				return value;
20479 			};
20480 
20481 			style = self.getEl().style;
20482 			rect = self._layoutRect;
20483 			lastRepaintRect = self._lastRepaintRect || {};
20484 
20485 			borderBox = self._borderBox;
20486 			borderW = borderBox.left + borderBox.right;
20487 			borderH = borderBox.top + borderBox.bottom;
20488 
20489 			if (rect.x !== lastRepaintRect.x) {
20490 				style.left = round(rect.x) + 'px';
20491 				lastRepaintRect.x = rect.x;
20492 			}
20493 
20494 			if (rect.y !== lastRepaintRect.y) {
20495 				style.top = round(rect.y) + 'px';
20496 				lastRepaintRect.y = rect.y;
20497 			}
20498 
20499 			if (rect.w !== lastRepaintRect.w) {
20500 				style.width = round(rect.w - borderW) + 'px';
20501 				lastRepaintRect.w = rect.w;
20502 			}
20503 
20504 			if (rect.h !== lastRepaintRect.h) {
20505 				style.height = round(rect.h - borderH) + 'px';
20506 				lastRepaintRect.h = rect.h;
20507 			}
20508 
20509 			// Update body if needed
20510 			if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
20511 				bodyStyle = self.getEl('body').style;
20512 				bodyStyle.width = round(rect.innerW) + 'px';
20513 				lastRepaintRect.innerW = rect.innerW;
20514 			}
20515 
20516 			if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
20517 				bodyStyle = bodyStyle || self.getEl('body').style;
20518 				bodyStyle.height = round(rect.innerH) + 'px';
20519 				lastRepaintRect.innerH = rect.innerH;
20520 			}
20521 
20522 			self._lastRepaintRect = lastRepaintRect;
20523 			self.fire('repaint', {}, false);
20524 		},
20525 
20526 		/**
20527 		 * Binds a callback to the specified event. This event can both be
20528 		 * native browser events like "click" or custom ones like PostRender.
20529 		 *
20530 		 * The callback function will be passed a DOM event like object that enables yout do stop propagation.
20531 		 *
20532 		 * @method on
20533 		 * @param {String} name Name of the event to bind. For example "click".
20534 		 * @param {String/function} callback Callback function to execute ones the event occurs.
20535 		 * @return {tinymce.ui.Control} Current control object.
20536 		 */
20537 		on: function(name, callback) {
20538 			var self = this;
20539 
20540 			function resolveCallbackName(name) {
20541 				var callback, scope;
20542 
20543 				if (typeof(name) != 'string') {
20544 					return name;
20545 				}
20546 
20547 				return function(e) {
20548 					if (!callback) {
20549 						self.parentsAndSelf().each(function(ctrl) {
20550 							var callbacks = ctrl.settings.callbacks;
20551 
20552 							if (callbacks && (callback = callbacks[name])) {
20553 								scope = ctrl;
20554 								return false;
20555 							}
20556 						});
20557 					}
20558 
20559 					return callback.call(scope, e);
20560 				};
20561 			}
20562 
20563 			getEventDispatcher(self).on(name, resolveCallbackName(callback));
20564 
20565 			return self;
20566 		},
20567 
20568 		/**
20569 		 * Unbinds the specified event and optionally a specific callback. If you omit the name
20570 		 * parameter all event handlers will be removed. If you omit the callback all event handles
20571 		 * by the specified name will be removed.
20572 		 *
20573 		 * @method off
20574 		 * @param {String} [name] Name for the event to unbind.
20575 		 * @param {function} [callback] Callback function to unbind.
20576 		 * @return {mxex.ui.Control} Current control object.
20577 		 */
20578 		off: function(name, callback) {
20579 			getEventDispatcher(this).off(name, callback);
20580 			return this;
20581 		},
20582 
20583 		/**
20584 		 * Fires the specified event by name and arguments on the control. This will execute all
20585 		 * bound event handlers.
20586 		 *
20587 		 * @method fire
20588 		 * @param {String} name Name of the event to fire.
20589 		 * @param {Object} [args] Arguments to pass to the event.
20590 		 * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
20591 		 * @return {Object} Current arguments object.
20592 		 */
20593 		fire: function(name, args, bubble) {
20594 			var self = this;
20595 
20596 			args = args || {};
20597 
20598 			if (!args.control) {
20599 				args.control = self;
20600 			}
20601 
20602 			args = getEventDispatcher(self).fire(name, args);
20603 
20604 			// Bubble event up to parents
20605 			if (bubble !== false && self.parent) {
20606 				var parent = self.parent();
20607 				while (parent && !args.isPropagationStopped()) {
20608 					parent.fire(name, args, false);
20609 					parent = parent.parent();
20610 				}
20611 			}
20612 
20613 			return args;
20614 		},
20615 
20616 		/**
20617 		 * Returns true/false if the specified event has any listeners.
20618 		 *
20619 		 * @method hasEventListeners
20620 		 * @param {String} name Name of the event to check for.
20621 		 * @return {Boolean} True/false state if the event has listeners.
20622 		 */
20623 		hasEventListeners: function(name) {
20624 			return getEventDispatcher(this).has(name);
20625 		},
20626 
20627 		/**
20628 		 * Returns a control collection with all parent controls.
20629 		 *
20630 		 * @method parents
20631 		 * @param {String} selector Optional selector expression to find parents.
20632 		 * @return {tinymce.ui.Collection} Collection with all parent controls.
20633 		 */
20634 		parents: function(selector) {
20635 			var self = this, ctrl, parents = new Collection();
20636 
20637 			// Add each parent to collection
20638 			for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
20639 				parents.add(ctrl);
20640 			}
20641 
20642 			// Filter away everything that doesn't match the selector
20643 			if (selector) {
20644 				parents = parents.filter(selector);
20645 			}
20646 
20647 			return parents;
20648 		},
20649 
20650 		/**
20651 		 * Returns the current control and it's parents.
20652 		 *
20653 		 * @method parentsAndSelf
20654 		 * @param {String} selector Optional selector expression to find parents.
20655 		 * @return {tinymce.ui.Collection} Collection with all parent controls.
20656 		 */
20657 		parentsAndSelf: function(selector) {
20658 			return new Collection(this).add(this.parents(selector));
20659 		},
20660 
20661 		/**
20662 		 * Returns the control next to the current control.
20663 		 *
20664 		 * @method next
20665 		 * @return {tinymce.ui.Control} Next control instance.
20666 		 */
20667 		next: function() {
20668 			var parentControls = this.parent().items();
20669 
20670 			return parentControls[parentControls.indexOf(this) + 1];
20671 		},
20672 
20673 		/**
20674 		 * Returns the control previous to the current control.
20675 		 *
20676 		 * @method prev
20677 		 * @return {tinymce.ui.Control} Previous control instance.
20678 		 */
20679 		prev: function() {
20680 			var parentControls = this.parent().items();
20681 
20682 			return parentControls[parentControls.indexOf(this) - 1];
20683 		},
20684 
20685 		/**
20686 		 * Find the common ancestor for two control instances.
20687 		 *
20688 		 * @method findCommonAncestor
20689 		 * @param {tinymce.ui.Control} ctrl1 First control.
20690 		 * @param {tinymce.ui.Control} ctrl2 Second control.
20691 		 * @return {tinymce.ui.Control} Ancestor control instance.
20692 		 */
20693 		findCommonAncestor: function(ctrl1, ctrl2) {
20694 			var parentCtrl;
20695 
20696 			while (ctrl1) {
20697 				parentCtrl = ctrl2;
20698 
20699 				while (parentCtrl && ctrl1 != parentCtrl) {
20700 					parentCtrl = parentCtrl.parent();
20701 				}
20702 
20703 				if (ctrl1 == parentCtrl) {
20704 					break;
20705 				}
20706 
20707 				ctrl1 = ctrl1.parent();
20708 			}
20709 
20710 			return ctrl1;
20711 		},
20712 
20713 		/**
20714 		 * Returns true/false if the specific control has the specific class.
20715 		 *
20716 		 * @method hasClass
20717 		 * @param {String} cls Class to check for.
20718 		 * @param {String} [group] Sub element group name.
20719 		 * @return {Boolean} True/false if the control has the specified class.
20720 		 */
20721 		hasClass: function(cls, group) {
20722 			var classes = this._classes[group || 'control'];
20723 
20724 			cls = this.classPrefix + cls;
20725 
20726 			return classes && !!classes.map[cls];
20727 		},
20728 
20729 		/**
20730 		 * Adds the specified class to the control
20731 		 *
20732 		 * @method addClass
20733 		 * @param {String} cls Class to check for.
20734 		 * @param {String} [group] Sub element group name.
20735 		 * @return {tinymce.ui.Control} Current control object.
20736 		 */
20737 		addClass: function(cls, group) {
20738 			var self = this, classes, elm;
20739 
20740 			cls = this.classPrefix + cls;
20741 			classes = self._classes[group || 'control'];
20742 
20743 			if (!classes) {
20744 				classes = [];
20745 				classes.map = {};
20746 				self._classes[group || 'control'] = classes;
20747 			}
20748 
20749 			if (!classes.map[cls]) {
20750 				classes.map[cls] = cls;
20751 				classes.push(cls);
20752 
20753 				if (self._rendered) {
20754 					elm = self.getEl(group);
20755 
20756 					if (elm) {
20757 						elm.className = classes.join(' ');
20758 					}
20759 				}
20760 			}
20761 
20762 			return self;
20763 		},
20764 
20765 		/**
20766 		 * Removes the specified class from the control.
20767 		 *
20768 		 * @method removeClass
20769 		 * @param {String} cls Class to remove.
20770 		 * @param {String} [group] Sub element group name.
20771 		 * @return {tinymce.ui.Control} Current control object.
20772 		 */
20773 		removeClass: function(cls, group) {
20774 			var self = this, classes, i, elm;
20775 
20776 			cls = this.classPrefix + cls;
20777 			classes = self._classes[group || 'control'];
20778 			if (classes && classes.map[cls]) {
20779 				delete classes.map[cls];
20780 
20781 				i = classes.length;
20782 				while (i--) {
20783 					if (classes[i] === cls) {
20784 						classes.splice(i, 1);
20785 					}
20786 				}
20787 			}
20788 
20789 			if (self._rendered) {
20790 				elm = self.getEl(group);
20791 
20792 				if (elm) {
20793 					elm.className = classes.join(' ');
20794 				}
20795 			}
20796 
20797 			return self;
20798 		},
20799 
20800 		/**
20801 		 * Toggles the specified class on the control.
20802 		 *
20803 		 * @method toggleClass
20804 		 * @param {String} cls Class to remove.
20805 		 * @param {Boolean} state True/false state to add/remove class.
20806 		 * @param {String} [group] Sub element group name.
20807 		 * @return {tinymce.ui.Control} Current control object.
20808 		 */
20809 		toggleClass: function(cls, state, group) {
20810 			var self = this;
20811 
20812 			if (state) {
20813 				self.addClass(cls, group);
20814 			} else {
20815 				self.removeClass(cls, group);
20816 			}
20817 
20818 			return self;
20819 		},
20820 
20821 		/**
20822 		 * Returns the class string for the specified group name.
20823 		 *
20824 		 * @method classes
20825 		 * @param {String} [group] Group to get clases by.
20826 		 * @return {String} Classes for the specified group.
20827 		 */
20828 		classes: function(group) {
20829 			var classes = this._classes[group || 'control'];
20830 
20831 			return classes ? classes.join(' ') : '';
20832 		},
20833 
20834 		/**
20835 		 * Sets the inner HTML of the control element.
20836 		 *
20837 		 * @method innerHtml
20838 		 * @param {String} html Html string to set as inner html.
20839 		 * @return {tinymce.ui.Control} Current control object.
20840 		 */
20841 		innerHtml: function(html) {
20842 			DomUtils.innerHtml(this.getEl(), html);
20843 			return this;
20844 		},
20845 
20846 		/**
20847 		 * Returns the control DOM element or sub element.
20848 		 *
20849 		 * @method getEl
20850 		 * @param {String} [suffix] Suffix to get element by.
20851 		 * @return {Element} HTML DOM element for the current control or it's children.
20852 		 */
20853 		getEl: function(suffix) {
20854 			var id = suffix ? this._id + '-' + suffix : this._id;
20855 
20856 			if (!this._elmCache[id]) {
20857 				this._elmCache[id] = DomUtils.get(id);
20858 			}
20859 
20860 			return this._elmCache[id];
20861 		},
20862 
20863 		/**
20864 		 * Sets/gets the visible for the control.
20865 		 *
20866 		 * @method visible
20867 		 * @param {Boolean} state Value to set to control.
20868 		 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
20869 		 */
20870 		visible: function(state) {
20871 			var self = this, parentCtrl;
20872 
20873 			if (typeof(state) !== "undefined") {
20874 				if (self._visible !== state) {
20875 					if (self._rendered) {
20876 						self.getEl().style.display = state ? '' : 'none';
20877 					}
20878 
20879 					self._visible = state;
20880 
20881 					// Parent container needs to reflow
20882 					parentCtrl = self.parent();
20883 					if (parentCtrl) {
20884 						parentCtrl._lastRect = null;
20885 					}
20886 
20887 					self.fire(state ? 'show' : 'hide');
20888 				}
20889 
20890 				return self;
20891 			}
20892 
20893 			return self._visible;
20894 		},
20895 
20896 		/**
20897 		 * Sets the visible state to true.
20898 		 *
20899 		 * @method show
20900 		 * @return {tinymce.ui.Control} Current control instance.
20901 		 */
20902 		show: function() {
20903 			return this.visible(true);
20904 		},
20905 
20906 		/**
20907 		 * Sets the visible state to false.
20908 		 *
20909 		 * @method hide
20910 		 * @return {tinymce.ui.Control} Current control instance.
20911 		 */
20912 		hide: function() {
20913 			return this.visible(false);
20914 		},
20915 
20916 		/**
20917 		 * Focuses the current control.
20918 		 *
20919 		 * @method focus
20920 		 * @return {tinymce.ui.Control} Current control instance.
20921 		 */
20922 		focus: function() {
20923 			try {
20924 				this.getEl().focus();
20925 			} catch (ex) {
20926 				// Ignore IE error
20927 			}
20928 
20929 			return this;
20930 		},
20931 
20932 		/**
20933 		 * Blurs the current control.
20934 		 *
20935 		 * @method blur
20936 		 * @return {tinymce.ui.Control} Current control instance.
20937 		 */
20938 		blur: function() {
20939 			this.getEl().blur();
20940 
20941 			return this;
20942 		},
20943 
20944 		/**
20945 		 * Sets the specified aria property.
20946 		 *
20947 		 * @method aria
20948 		 * @param {String} name Name of the aria property to set.
20949 		 * @param {String} value Value of the aria property.
20950 		 * @return {tinymce.ui.Control} Current control instance.
20951 		 */
20952 		aria: function(name, value) {
20953 			var self = this, elm = self.getEl(self.ariaTarget);
20954 
20955 			if (typeof(value) === "undefined") {
20956 				return self._aria[name];
20957 			} else {
20958 				self._aria[name] = value;
20959 			}
20960 
20961 			if (self._rendered) {
20962 				elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
20963 			}
20964 
20965 			return self;
20966 		},
20967 
20968 		/**
20969 		 * Encodes the specified string with HTML entities. It will also
20970 		 * translate the string to different languages.
20971 		 *
20972 		 * @method encode
20973 		 * @param {String/Object/Array} text Text to entity encode.
20974 		 * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
20975 		 * @return {String} Encoded and possible traslated string.
20976 		 */
20977 		encode: function(text, translate) {
20978 			if (translate !== false) {
20979 				text = this.translate(text);
20980 			}
20981 
20982 			return (text || '').replace(/[&<>"]/g, function(match) {
20983 				return '&#' + match.charCodeAt(0) + ';';
20984 			});
20985 		},
20986 
20987 		/**
20988 		 * Returns the translated string.
20989 		 *
20990 		 * @method translate
20991 		 * @param {String} text Text to translate.
20992 		 * @return {String} Translated string or the same as the input.
20993 		 */
20994 		translate: function(text) {
20995 			return Control.translate ? Control.translate(text) : text;
20996 		},
20997 
20998 		/**
20999 		 * Adds items before the current control.
21000 		 *
21001 		 * @method before
21002 		 * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
21003 		 * @return {tinymce.ui.Control} Current control instance.
21004 		 */
21005 		before: function(items) {
21006 			var self = this, parent = self.parent();
21007 
21008 			if (parent) {
21009 				parent.insert(items, parent.items().indexOf(self), true);
21010 			}
21011 
21012 			return self;
21013 		},
21014 
21015 		/**
21016 		 * Adds items after the current control.
21017 		 *
21018 		 * @method after
21019 		 * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
21020 		 * @return {tinymce.ui.Control} Current control instance.
21021 		 */
21022 		after: function(items) {
21023 			var self = this, parent = self.parent();
21024 
21025 			if (parent) {
21026 				parent.insert(items, parent.items().indexOf(self));
21027 			}
21028 
21029 			return self;
21030 		},
21031 
21032 		/**
21033 		 * Removes the current control from DOM and from UI collections.
21034 		 *
21035 		 * @method remove
21036 		 * @return {tinymce.ui.Control} Current control instance.
21037 		 */
21038 		remove: function() {
21039 			var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
21040 
21041 			if (self.items) {
21042 				var controls = self.items().toArray();
21043 				i = controls.length;
21044 				while (i--) {
21045 					controls[i].remove();
21046 				}
21047 			}
21048 
21049 			if (parent && parent.items) {
21050 				newItems = [];
21051 
21052 				parent.items().each(function(item) {
21053 					if (item !== self) {
21054 						newItems.push(item);
21055 					}
21056 				});
21057 
21058 				parent.items().set(newItems);
21059 				parent._lastRect = null;
21060 			}
21061 
21062 			if (self._eventsRoot && self._eventsRoot == self) {
21063 				DomUtils.off(elm);
21064 			}
21065 
21066 			var lookup = self.getRoot().controlIdLookup;
21067 			if (lookup) {
21068 				delete lookup[self._id];
21069 			}
21070 
21071 			if (elm && elm.parentNode) {
21072 				elm.parentNode.removeChild(elm);
21073 			}
21074 
21075 			self._rendered = false;
21076 
21077 			return self;
21078 		},
21079 
21080 		/**
21081 		 * Renders the control before the specified element.
21082 		 *
21083 		 * @method renderBefore
21084 		 * @param {Element} elm Element to render before.
21085 		 * @return {tinymce.ui.Control} Current control instance.
21086 		 */
21087 		renderBefore: function(elm) {
21088 			var self = this;
21089 
21090 			elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
21091 			self.postRender();
21092 
21093 			return self;
21094 		},
21095 
21096 		/**
21097 		 * Renders the control to the specified element.
21098 		 *
21099 		 * @method renderBefore
21100 		 * @param {Element} elm Element to render to.
21101 		 * @return {tinymce.ui.Control} Current control instance.
21102 		 */
21103 		renderTo: function(elm) {
21104 			var self = this;
21105 
21106 			elm = elm || self.getContainerElm();
21107 			elm.appendChild(DomUtils.createFragment(self.renderHtml()));
21108 			self.postRender();
21109 
21110 			return self;
21111 		},
21112 
21113 		/**
21114 		 * Post render method. Called after the control has been rendered to the target.
21115 		 *
21116 		 * @method postRender
21117 		 * @return {tinymce.ui.Control} Current control instance.
21118 		 */
21119 		postRender: function() {
21120 			var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
21121 
21122 			// Bind on<event> settings
21123 			for (name in settings) {
21124 				if (name.indexOf("on") === 0) {
21125 					self.on(name.substr(2), settings[name]);
21126 				}
21127 			}
21128 
21129 			if (self._eventsRoot) {
21130 				for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
21131 					parentEventsRoot = parent._eventsRoot;
21132 				}
21133 
21134 				if (parentEventsRoot) {
21135 					for (name in parentEventsRoot._nativeEvents) {
21136 						self._nativeEvents[name] = true;
21137 					}
21138 				}
21139 			}
21140 
21141 			self.bindPendingEvents();
21142 
21143 			if (settings.style) {
21144 				elm = self.getEl();
21145 				if (elm) {
21146 					elm.setAttribute('style', settings.style);
21147 					elm.style.cssText = settings.style;
21148 				}
21149 			}
21150 
21151 			if (!self._visible) {
21152 				DomUtils.css(self.getEl(), 'display', 'none');
21153 			}
21154 
21155 			if (self.settings.border) {
21156 				box = self.borderBox();
21157 				DomUtils.css(self.getEl(), {
21158 					'border-top-width': box.top,
21159 					'border-right-width': box.right,
21160 					'border-bottom-width': box.bottom,
21161 					'border-left-width': box.left
21162 				});
21163 			}
21164 
21165 			// Add instance to lookup
21166 			var root = self.getRoot();
21167 			if (!root.controlIdLookup) {
21168 				root.controlIdLookup = {};
21169 			}
21170 
21171 			root.controlIdLookup[self._id] = self;
21172 
21173 			for (var key in self._aria) {
21174 				self.aria(key, self._aria[key]);
21175 			}
21176 
21177 			self.fire('postrender', {}, false);
21178 		},
21179 
21180 		/**
21181 		 * Scrolls the current control into view.
21182 		 *
21183 		 * @method scrollIntoView
21184 		 * @param {String} align Alignment in view top|center|bottom.
21185 		 * @return {tinymce.ui.Control} Current control instance.
21186 		 */
21187 		scrollIntoView: function(align) {
21188 			function getOffset(elm, rootElm) {
21189 				var x, y, parent = elm;
21190 
21191 				x = y = 0;
21192 				while (parent && parent != rootElm && parent.nodeType) {
21193 					x += parent.offsetLeft || 0;
21194 					y += parent.offsetTop || 0;
21195 					parent = parent.offsetParent;
21196 				}
21197 
21198 				return {x: x, y: y};
21199 			}
21200 
21201 			var elm = this.getEl(), parentElm = elm.parentNode;
21202 			var x, y, width, height, parentWidth, parentHeight;
21203 			var pos = getOffset(elm, parentElm);
21204 
21205 			x = pos.x;
21206 			y = pos.y;
21207 			width = elm.offsetWidth;
21208 			height = elm.offsetHeight;
21209 			parentWidth = parentElm.clientWidth;
21210 			parentHeight = parentElm.clientHeight;
21211 
21212 			if (align == "end") {
21213 				x -= parentWidth - width;
21214 				y -= parentHeight - height;
21215 			} else if (align == "center") {
21216 				x -= (parentWidth / 2) - (width / 2);
21217 				y -= (parentHeight / 2) - (height / 2);
21218 			}
21219 
21220 			parentElm.scrollLeft = x;
21221 			parentElm.scrollTop = y;
21222 
21223 			return this;
21224 		},
21225 
21226 		/**
21227 		 * Binds pending DOM events.
21228 		 *
21229 		 * @private
21230 		 */
21231 		bindPendingEvents: function() {
21232 			var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
21233 
21234 			function delegate(e) {
21235 				var control = self.getParentCtrl(e.target);
21236 
21237 				if (control) {
21238 					control.fire(e.type, e);
21239 				}
21240 			}
21241 
21242 			function mouseLeaveHandler() {
21243 				var ctrl = eventRootCtrl._lastHoverCtrl;
21244 
21245 				if (ctrl) {
21246 					ctrl.fire("mouseleave", {target: ctrl.getEl()});
21247 
21248 					ctrl.parents().each(function(ctrl) {
21249 						ctrl.fire("mouseleave", {target: ctrl.getEl()});
21250 					});
21251 
21252 					eventRootCtrl._lastHoverCtrl = null;
21253 				}
21254 			}
21255 
21256 			function mouseEnterHandler(e) {
21257 				var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
21258 
21259 				// Over on a new control
21260 				if (ctrl !== lastCtrl) {
21261 					eventRootCtrl._lastHoverCtrl = ctrl;
21262 
21263 					parents = ctrl.parents().toArray().reverse();
21264 					parents.push(ctrl);
21265 
21266 					if (lastCtrl) {
21267 						lastParents = lastCtrl.parents().toArray().reverse();
21268 						lastParents.push(lastCtrl);
21269 
21270 						for (idx = 0; idx < lastParents.length; idx++) {
21271 							if (parents[idx] !== lastParents[idx]) {
21272 								break;
21273 							}
21274 						}
21275 
21276 						for (i = lastParents.length - 1; i >= idx; i--) {
21277 							lastCtrl = lastParents[i];
21278 							lastCtrl.fire("mouseleave", {
21279 								target : lastCtrl.getEl()
21280 							});
21281 						}
21282 					}
21283 
21284 					for (i = idx; i < parents.length; i++) {
21285 						ctrl = parents[i];
21286 						ctrl.fire("mouseenter", {
21287 							target : ctrl.getEl()
21288 						});
21289 					}
21290 				}
21291 			}
21292 
21293 			function fixWheelEvent(e) {
21294 				e.preventDefault();
21295 
21296 				if (e.type == "mousewheel") {
21297 					e.deltaY = -1 / 40 * e.wheelDelta;
21298 
21299 					if (e.wheelDeltaX) {
21300 						e.deltaX = -1 / 40 * e.wheelDeltaX;
21301 					}
21302 				} else {
21303 					e.deltaX = 0;
21304 					e.deltaY = e.detail;
21305 				}
21306 
21307 				e = self.fire("wheel", e);
21308 			}
21309 
21310 			self._rendered = true;
21311 
21312 			nativeEvents = self._nativeEvents;
21313 			if (nativeEvents) {
21314 				// Find event root element if it exists
21315 				parents = self.parents().toArray();
21316 				parents.unshift(self);
21317 				for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
21318 					eventRootCtrl = parents[i]._eventsRoot;
21319 				}
21320 
21321 				// Event root wasn't found the use the root control
21322 				if (!eventRootCtrl) {
21323 					eventRootCtrl = parents[parents.length - 1] || self;
21324 				}
21325 
21326 				// Set the eventsRoot property on children that didn't have it
21327 				self._eventsRoot = eventRootCtrl;
21328 				for (l = i, i = 0; i < l; i++) {
21329 					parents[i]._eventsRoot = eventRootCtrl;
21330 				}
21331 
21332 				var eventRootDelegates = eventRootCtrl._delegates;
21333 				if (!eventRootDelegates) {
21334 					eventRootDelegates = eventRootCtrl._delegates = {};
21335 				}
21336 
21337 				// Bind native event delegates
21338 				for (name in nativeEvents) {
21339 					if (!nativeEvents) {
21340 						return false;
21341 					}
21342 
21343 					if (name === "wheel" && !hasWheelEventSupport) {
21344 						if (hasMouseWheelEventSupport) {
21345 							DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
21346 						} else {
21347 							DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
21348 						}
21349 
21350 						continue;
21351 					}
21352 
21353 					// Special treatment for mousenter/mouseleave since these doesn't bubble
21354 					if (name === "mouseenter" || name === "mouseleave") {
21355 						// Fake mousenter/mouseleave
21356 						if (!eventRootCtrl._hasMouseEnter) {
21357 							DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
21358 							DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
21359 							eventRootCtrl._hasMouseEnter = 1;
21360 						}
21361 					} else if (!eventRootDelegates[name]) {
21362 						DomUtils.on(eventRootCtrl.getEl(), name, delegate);
21363 						eventRootDelegates[name] = true;
21364 					}
21365 
21366 					// Remove the event once it's bound
21367 					nativeEvents[name] = false;
21368 				}
21369 			}
21370 		},
21371 
21372 		getRoot: function() {
21373 			var ctrl = this, rootControl, parents = [];
21374 
21375 			while (ctrl) {
21376 				if (ctrl.rootControl) {
21377 					rootControl = ctrl.rootControl;
21378 					break;
21379 				}
21380 
21381 				parents.push(ctrl);
21382 				rootControl = ctrl;
21383 				ctrl = ctrl.parent();
21384 			}
21385 
21386 			if (!rootControl) {
21387 				rootControl = this;
21388 			}
21389 
21390 			var i = parents.length;
21391 			while (i--) {
21392 				parents[i].rootControl = rootControl;
21393 			}
21394 
21395 			return rootControl;
21396 		},
21397 
21398 		/**
21399 		 * Reflows the current control and it's parents.
21400 		 * This should be used after you for example append children to the current control so
21401 		 * that the layout managers know that they need to reposition everything.
21402 		 *
21403 		 * @example
21404 		 * container.append({type: 'button', text: 'My button'}).reflow();
21405 		 *
21406 		 * @method reflow
21407 		 * @return {tinymce.ui.Control} Current control instance.
21408 		 */
21409 		reflow: function() {
21410 			this.repaint();
21411 
21412 			return this;
21413 		}
21414 
21415 		/**
21416 		 * Sets/gets the parent container for the control.
21417 		 *
21418 		 * @method parent
21419 		 * @param {tinymce.ui.Container} parent Optional parent to set.
21420 		 * @return {tinymce.ui.Control} Parent control or the current control on a set action.
21421 		 */
21422 		// parent: function(parent) {} -- Generated
21423 
21424 		/**
21425 		 * Sets/gets the text for the control.
21426 		 *
21427 		 * @method text
21428 		 * @param {String} value Value to set to control.
21429 		 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
21430 		 */
21431 		// text: function(value) {} -- Generated
21432 
21433 		/**
21434 		 * Sets/gets the width for the control.
21435 		 *
21436 		 * @method width
21437 		 * @param {Number} value Value to set to control.
21438 		 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
21439 		 */
21440 		// width: function(value) {} -- Generated
21441 
21442 		/**
21443 		 * Sets/gets the height for the control.
21444 		 *
21445 		 * @method height
21446 		 * @param {Number} value Value to set to control.
21447 		 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
21448 		 */
21449 		// height: function(value) {} -- Generated
21450 
21451 		/**
21452 		 * Sets/gets the disabled state on the control.
21453 		 *
21454 		 * @method disabled
21455 		 * @param {Boolean} state Value to set to control.
21456 		 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
21457 		 */
21458 		// disabled: function(state) {} -- Generated
21459 
21460 		/**
21461 		 * Sets/gets the active for the control.
21462 		 *
21463 		 * @method active
21464 		 * @param {Boolean} state Value to set to control.
21465 		 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
21466 		 */
21467 		// active: function(state) {} -- Generated
21468 
21469 		/**
21470 		 * Sets/gets the name for the control.
21471 		 *
21472 		 * @method name
21473 		 * @param {String} value Value to set to control.
21474 		 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
21475 		 */
21476 		// name: function(value) {} -- Generated
21477 
21478 		/**
21479 		 * Sets/gets the title for the control.
21480 		 *
21481 		 * @method title
21482 		 * @param {String} value Value to set to control.
21483 		 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
21484 		 */
21485 		// title: function(value) {} -- Generated
21486 	});
21487 
21488 	return Control;
21489 });
21490 
21491 // Included from: js/tinymce/classes/ui/Factory.js
21492 
21493 /**
21494  * Factory.js
21495  *
21496  * Copyright, Moxiecode Systems AB
21497  * Released under LGPL License.
21498  *
21499  * License: http://www.tinymce.com/license
21500  * Contributing: http://www.tinymce.com/contributing
21501  */
21502 
21503 /*global tinymce:true */
21504 
21505 /**
21506  * This class is a factory for control instances. This enables you
21507  * to create instances of controls without having to require the UI controls directly.
21508  *
21509  * It also allow you to override or add new control types.
21510  *
21511  * @class tinymce.ui.Factory
21512  */
21513 define("tinymce/ui/Factory", [], function() {
21514 	"use strict";
21515 
21516 	var types = {}, namespaceInit;
21517 
21518 	return {
21519 		/**
21520 		 * Adds a new control instance type to the factory.
21521 		 *
21522 		 * @method add
21523 		 * @param {String} type Type name for example "button".
21524 		 * @param {function} typeClass Class type function.
21525 		 */
21526 		add: function(type, typeClass) {
21527 			types[type.toLowerCase()] = typeClass;
21528 		},
21529 
21530 		/**
21531 		 * Returns true/false if the specified type exists or not.
21532 		 *
21533 		 * @method has
21534 		 * @param {String} type Type to look for.
21535 		 * @return {Boolean} true/false if the control by name exists.
21536 		 */
21537 		has: function(type) {
21538 			return !!types[type.toLowerCase()];
21539 		},
21540 
21541 		/**
21542 		 * Creates a new control instance based on the settings provided. The instance created will be
21543 		 * based on the specified type property it can also create whole structures of components out of
21544 		 * the specified JSON object.
21545 		 *
21546 		 * @example
21547 		 * tinymce.ui.Factory.create({
21548 		 *     type: 'button',
21549 		 *     text: 'Hello world!'
21550 		 * });
21551 		 *
21552 		 * @method create
21553 		 * @param {Object/String} settings Name/Value object with items used to create the type.
21554 		 * @return {tinymce.ui.Control} Control instance based on the specified type.
21555 		 */
21556 		create: function(type, settings) {
21557 			var ControlType, name, namespace;
21558 
21559 			// Build type lookup
21560 			if (!namespaceInit) {
21561 				namespace = tinymce.ui;
21562 
21563 				for (name in namespace) {
21564 					types[name.toLowerCase()] = namespace[name];
21565 				}
21566 
21567 				namespaceInit = true;
21568 			}
21569 
21570 			// If string is specified then use it as the type
21571 			if (typeof(type) == 'string') {
21572 				settings = settings || {};
21573 				settings.type = type;
21574 			} else {
21575 				settings = type;
21576 				type = settings.type;
21577 			}
21578 
21579 			// Find control type
21580 			type = type.toLowerCase();
21581 			ControlType = types[type];
21582 
21583 			// #if debug
21584 
21585 			if (!ControlType) {
21586 				throw new Error("Could not find control by type: " + type);
21587 			}
21588 
21589 			// #endif
21590 
21591 			ControlType = new ControlType(settings);
21592 			ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
21593 
21594 			return ControlType;
21595 		}
21596 	};
21597 });
21598 
21599 // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
21600 
21601 /**
21602  * KeyboardNavigation.js
21603  *
21604  * Copyright, Moxiecode Systems AB
21605  * Released under LGPL License.
21606  *
21607  * License: http://www.tinymce.com/license
21608  * Contributing: http://www.tinymce.com/contributing
21609  */
21610 
21611 /**
21612  * This class handles keyboard navigation of controls and elements.
21613  *
21614  * @class tinymce.ui.KeyboardNavigation
21615  */
21616 define("tinymce/ui/KeyboardNavigation", [
21617 ], function() {
21618 	"use strict";
21619 
21620 	/**
21621 	 * This class handles all keyboard navigation for WAI-ARIA support. Each root container
21622 	 * gets an instance of this class.
21623 	 *
21624 	 * @constructor
21625 	 */
21626 	return function(settings) {
21627 		var root = settings.root, focusedElement, focusedControl;
21628 
21629 		try {
21630 			focusedElement = document.activeElement;
21631 		} catch (ex) {
21632 			// IE sometimes fails to return a proper element
21633 			focusedElement = document.body;
21634 		}
21635 
21636 		focusedControl = root.getParentCtrl(focusedElement);
21637 
21638 		/**
21639 		 * Returns the currently focused elements wai aria role of the currently
21640 		 * focused element or specified element.
21641 		 *
21642 		 * @private
21643 		 * @param {Element} elm Optional element to get role from.
21644 		 * @return {String} Role of specified element.
21645 		 */
21646 		function getRole(elm) {
21647 			elm = elm || focusedElement;
21648 
21649 			return elm && elm.getAttribute('role');
21650 		}
21651 
21652 		/**
21653 		 * Returns the wai role of the parent element of the currently
21654 		 * focused element or specified element.
21655 		 *
21656 		 * @private
21657 		 * @param {Element} elm Optional element to get parent role from.
21658 		 * @return {String} Role of the first parent that has a role.
21659 		 */
21660 		function getParentRole(elm) {
21661 			var role, parent = elm || focusedElement;
21662 
21663 			while ((parent = parent.parentNode)) {
21664 				if ((role = getRole(parent))) {
21665 					return role;
21666 				}
21667 			}
21668 		}
21669 
21670 		/**
21671 		 * Returns a wai aria property by name for example aria-selected.
21672 		 *
21673 		 * @private
21674 		 * @param {String} name Name of the aria property to get for example "disabled".
21675 		 * @return {String} Aria property value.
21676 		 */
21677 		function getAriaProp(name) {
21678 			var elm = focusedElement;
21679 
21680 			if (elm) {
21681 				return elm.getAttribute('aria-' + name);
21682 			}
21683 		}
21684 
21685 		/**
21686 		 * Is the element a text input element or not.
21687 		 *
21688 		 * @private
21689 		 * @param {Element} elm Element to check if it's an text input element or not.
21690 		 * @return {Boolean} True/false if the element is a text element or not.
21691 		 */
21692 		function isTextInputElement(elm) {
21693 			var tagName = elm.tagName.toUpperCase();
21694 
21695 			// Notice: since type can be "email" etc we don't check the type
21696 			// So all input elements gets treated as text input elements
21697 			return tagName == "INPUT" || tagName == "TEXTAREA";
21698 		}
21699 
21700 		/**
21701 		 * Returns true/false if the specified element can be focused or not.
21702 		 *
21703 		 * @private
21704 		 * @param {Element} elm DOM element to check if it can be focused or not.
21705 		 * @return {Boolean} True/false if the element can have focus.
21706 		 */
21707 		function canFocus(elm) {
21708 			if (isTextInputElement(elm) && !elm.hidden) {
21709 				return true;
21710 			}
21711 
21712 			if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
21713 				return true;
21714 			}
21715 
21716 			return false;
21717 		}
21718 
21719 		/**
21720 		 * Returns an array of focusable visible elements within the specified container element.
21721 		 *
21722 		 * @private
21723 		 * @param {Element} elm DOM element to find focusable elements within.
21724 		 * @return {Array} Array of focusable elements.
21725 		 */
21726 		function getFocusElements(elm) {
21727 			var elements = [];
21728 
21729 			function collect(elm) {
21730 				if (elm.nodeType != 1 || elm.style.display == 'none') {
21731 					return;
21732 				}
21733 
21734 				if (canFocus(elm)) {
21735 					elements.push(elm);
21736 				}
21737 
21738 				for (var i = 0; i < elm.childNodes.length; i++) {
21739 					collect(elm.childNodes[i]);
21740 				}
21741 			}
21742 
21743 			collect(elm || root.getEl());
21744 
21745 			return elements;
21746 		}
21747 
21748 		/**
21749 		 * Returns the navigation root control for the specified control. The navigation root
21750 		 * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
21751 		 * It will look for parents of the specified target control or the currently focused control if this option is omitted.
21752 		 *
21753 		 * @private
21754 		 * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
21755 		 * @return {tinymce.ui.Control} Navigation root control.
21756 		 */
21757 		function getNavigationRoot(targetControl) {
21758 			var navigationRoot, controls;
21759 
21760 			targetControl = targetControl || focusedControl;
21761 			controls = targetControl.parents().toArray();
21762 			controls.unshift(targetControl);
21763 
21764 			for (var i = 0; i < controls.length; i++) {
21765 				navigationRoot = controls[i];
21766 
21767 				if (navigationRoot.settings.ariaRoot) {
21768 					break;
21769 				}
21770 			}
21771 
21772 			return navigationRoot;
21773 		}
21774 
21775 		/**
21776 		 * Focuses the first item in the specified targetControl element or the last aria index if the
21777 		 * navigation root has the ariaRemember option enabled.
21778 		 *
21779 		 * @private
21780 		 * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
21781 		 */
21782 		function focusFirst(targetControl) {
21783 			var navigationRoot = getNavigationRoot(targetControl);
21784 			var focusElements = getFocusElements(navigationRoot.getEl());
21785 
21786 			if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
21787 				moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
21788 			} else {
21789 				moveFocusToIndex(0, focusElements);
21790 			}
21791 		}
21792 
21793 		/**
21794 		 * Moves the focus to the specified index within the elements list.
21795 		 * This will scope the index to the size of the element list if it changed.
21796 		 *
21797 		 * @private
21798 		 * @param {Number} idx Specified index to move to.
21799 		 * @param {Array} elements Array with dom elements to move focus within.
21800 		 * @return {Number} Input index or a changed index if it was out of range.
21801 		 */
21802 		function moveFocusToIndex(idx, elements) {
21803 			if (idx < 0) {
21804 				idx = elements.length - 1;
21805 			} else if (idx >= elements.length) {
21806 				idx = 0;
21807 			}
21808 
21809 			if (elements[idx]) {
21810 				elements[idx].focus();
21811 			}
21812 
21813 			return idx;
21814 		}
21815 
21816 		/**
21817 		 * Moves the focus forwards or backwards.
21818 		 *
21819 		 * @private
21820 		 * @param {Number} dir Direction to move in positive means forward, negative means backwards.
21821 		 * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
21822 		 *
21823 		 * @return {Boolean} Whether focus moved.
21824 		 */
21825 		function moveFocus(dir, elements) {
21826 			var idx = -1, navigationRoot = getNavigationRoot();
21827 
21828 			elements = elements || getFocusElements(navigationRoot.getEl());
21829 
21830 			for (var i = 0; i < elements.length; i++) {
21831 				if (elements[i] === focusedElement) {
21832 					idx = i;
21833 				}
21834 			}
21835 
21836 			idx += dir;
21837 
21838 			if (!navigationRoot.settings.wrapFocus && (idx < 0 || idx >= elements.length)) {
21839 				return false;
21840 			}
21841 
21842 			navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
21843 
21844 			return true;
21845 		}
21846 
21847 		/**
21848 		 * Moves the focus to the left this is called by the left key.
21849 		 *
21850 		 * @private
21851 		 */
21852 		function left() {
21853 			var parentRole = getParentRole();
21854 
21855 			if (parentRole == "tablist") {
21856 				moveFocus(-1, getFocusElements(focusedElement.parentNode));
21857 			} else if (focusedControl.parent().submenu) {
21858 				cancel();
21859 			} else {
21860 				moveFocus(-1);
21861 			}
21862 		}
21863 
21864 		/**
21865 		 * Moves the focus to the right this is called by the right key.
21866 		 *
21867 		 * @private
21868 		 */
21869 		function right() {
21870 			var role = getRole(), parentRole = getParentRole();
21871 
21872 			if (parentRole == "tablist") {
21873 				moveFocus(1, getFocusElements(focusedElement.parentNode));
21874 			} else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
21875 				enter();
21876 			} else {
21877 				moveFocus(1);
21878 			}
21879 		}
21880 
21881 		/**
21882 		 * Moves the focus to the up this is called by the up key.
21883 		 *
21884 		 * @private
21885 		 */
21886 		function up() {
21887 			moveFocus(-1);
21888 		}
21889 
21890 		/**
21891 		 * Moves the focus to the up this is called by the down key.
21892 		 *
21893 		 * @private
21894 		 */
21895 		function down() {
21896 			var role = getRole(), parentRole = getParentRole();
21897 
21898 			if (role == "menuitem" && parentRole == "menubar") {
21899 				enter();
21900 			} else if (role == "button" && getAriaProp('haspopup')) {
21901 				enter({key: 'down'});
21902 			} else {
21903 				moveFocus(1);
21904 			}
21905 		}
21906 
21907 		/**
21908 		 * Moves the focus to the next item or previous item depending on shift key.
21909 		 *
21910 		 * @private
21911 		 * @param {DOMEvent} e DOM event object.
21912 		 */
21913 		function tab(e) {
21914 			var parentRole = getParentRole();
21915 
21916 			if (parentRole == "tablist") {
21917 				var elm = getFocusElements(focusedControl.getEl('body'))[0];
21918 
21919 				if (elm) {
21920 					elm.focus();
21921 				}
21922 
21923 				return true;
21924 			} else {
21925 				return moveFocus(e.shiftKey ? -1 : 1);
21926 			}
21927 		}
21928 
21929 		/**
21930 		 * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
21931 		 *
21932 		 * @private
21933 		 */
21934 		function cancel() {
21935 			return focusedControl.fire('cancel');
21936 		}
21937 
21938 		/**
21939 		 * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
21940 		 *
21941 		 * @private
21942 		 * @param {Object} aria Optional aria data to pass along with the enter event.
21943 		 */
21944 		function enter(aria) {
21945 			aria = aria || {};
21946 			focusedControl.fire('click', {target: focusedElement, aria: aria});
21947 		}
21948 
21949 		root.on('keydown', function(e) {
21950 			function handleNonTabOrEscEvent(e, handler) {
21951 				// Ignore non tab keys for text elements
21952 				if (isTextInputElement(focusedElement)) {
21953 					return;
21954 				}
21955 
21956 				if (handler(e) !== false) {
21957 					e.preventDefault();
21958 				}
21959 			}
21960 
21961 			if (e.isDefaultPrevented()) {
21962 				return;
21963 			}
21964 
21965 			switch (e.keyCode) {
21966 				case 37: // DOM_VK_LEFT
21967 					handleNonTabOrEscEvent(e, left);
21968 					break;
21969 
21970 				case 39: // DOM_VK_RIGHT
21971 					handleNonTabOrEscEvent(e, right);
21972 					break;
21973 
21974 				case 38: // DOM_VK_UP
21975 					handleNonTabOrEscEvent(e, up);
21976 					break;
21977 
21978 				case 40: // DOM_VK_DOWN
21979 					handleNonTabOrEscEvent(e, down);
21980 					break;
21981 
21982 				case 27: // DOM_VK_ESCAPE
21983 					var cancelEv = cancel();
21984 					if (cancelEv.isDefaultPrevented()) {
21985 						e.preventDefault();
21986 						e.stopPropagation();
21987 					}
21988 					if (cancelEv.isPropagationStopped()) {
21989 						e.stopPropagation();
21990 					}
21991 					break;
21992 
21993 				case 14: // DOM_VK_ENTER
21994 				case 13: // DOM_VK_RETURN
21995 				case 32: // DOM_VK_SPACE
21996 					handleNonTabOrEscEvent(e, enter);
21997 					break;
21998 
21999 				case 9: // DOM_VK_TAB
22000 					if (tab(e) !== false) {
22001 						e.preventDefault();
22002 						e.stopPropagation();
22003 					}
22004 					break;
22005 			}
22006 		});
22007 
22008 		root.on('focusin', function(e) {
22009 			focusedElement = e.target;
22010 			focusedControl = e.control;
22011 		});
22012 
22013 		return {
22014 			focusFirst: focusFirst
22015 		};
22016 	};
22017 });
22018 
22019 // Included from: js/tinymce/classes/ui/Container.js
22020 
22021 /**
22022  * Container.js
22023  *
22024  * Copyright, Moxiecode Systems AB
22025  * Released under LGPL License.
22026  *
22027  * License: http://www.tinymce.com/license
22028  * Contributing: http://www.tinymce.com/contributing
22029  */
22030 
22031 /**
22032  * Container control. This is extended by all controls that can have
22033  * children such as panels etc. You can also use this class directly as an
22034  * generic container instance. The container doesn't have any specific role or style.
22035  *
22036  * @-x-less Container.less
22037  * @class tinymce.ui.Container
22038  * @extends tinymce.ui.Control
22039  */
22040 define("tinymce/ui/Container", [
22041 	"tinymce/ui/Control",
22042 	"tinymce/ui/Collection",
22043 	"tinymce/ui/Selector",
22044 	"tinymce/ui/Factory",
22045 	"tinymce/ui/KeyboardNavigation",
22046 	"tinymce/util/Tools",
22047 	"tinymce/ui/DomUtils"
22048 ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
22049 	"use strict";
22050 
22051 	var selectorCache = {};
22052 
22053 	return Control.extend({
22054 		layout: '',
22055 		innerClass: 'container-inner',
22056 
22057 		/**
22058 		 * Constructs a new control instance with the specified settings.
22059 		 *
22060 		 * @constructor
22061 		 * @param {Object} settings Name/value object with settings.
22062 		 * @setting {Array} items Items to add to container in JSON format or control instances.
22063 		 * @setting {String} layout Layout manager by name to use.
22064 		 * @setting {Object} defaults Default settings to apply to all items.
22065 		 */
22066 		init: function(settings) {
22067 			var self = this;
22068 
22069 			self._super(settings);
22070 			settings = self.settings;
22071 			self._fixed = settings.fixed;
22072 			self._items = new Collection();
22073 
22074 			if (self.isRtl()) {
22075 				self.addClass('rtl');
22076 			}
22077 
22078 			self.addClass('container');
22079 			self.addClass('container-body', 'body');
22080 
22081 			if (settings.containerCls) {
22082 				self.addClass(settings.containerCls);
22083 			}
22084 
22085 			self._layout = Factory.create((settings.layout || self.layout) + 'layout');
22086 
22087 			if (self.settings.items) {
22088 				self.add(self.settings.items);
22089 			}
22090 
22091 			// TODO: Fix this!
22092 			self._hasBody = true;
22093 		},
22094 
22095 		/**
22096 		 * Returns a collection of child items that the container currently have.
22097 		 *
22098 		 * @method items
22099 		 * @return {tinymce.ui.Collection} Control collection direct child controls.
22100 		 */
22101 		items: function() {
22102 			return this._items;
22103 		},
22104 
22105 		/**
22106 		 * Find child controls by selector.
22107 		 *
22108 		 * @method find
22109 		 * @param {String} selector Selector CSS pattern to find children by.
22110 		 * @return {tinymce.ui.Collection} Control collection with child controls.
22111 		 */
22112 		find: function(selector) {
22113 			selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
22114 
22115 			return selector.find(this);
22116 		},
22117 
22118 		/**
22119 		 * Adds one or many items to the current container. This will create instances of
22120 		 * the object representations if needed.
22121 		 *
22122 		 * @method add
22123 		 * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
22124 		 * @return {tinymce.ui.Collection} Current collection control.
22125 		 */
22126 		add: function(items) {
22127 			var self = this;
22128 
22129 			self.items().add(self.create(items)).parent(self);
22130 
22131 			return self;
22132 		},
22133 
22134 		/**
22135 		 * Focuses the current container instance. This will look
22136 		 * for the first control in the container and focus that.
22137 		 *
22138 		 * @method focus
22139 		 * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
22140 		 * @return {tinymce.ui.Collection} Current instance.
22141 		 */
22142 		focus: function(keyboard) {
22143 			var self = this, focusCtrl, keyboardNav, items;
22144 
22145 			if (keyboard) {
22146 				keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
22147 
22148 				if (keyboardNav) {
22149 					keyboardNav.focusFirst(self);
22150 					return;
22151 				}
22152 			}
22153 
22154 			items = self.find('*');
22155 
22156 			// TODO: Figure out a better way to auto focus alert dialog buttons
22157 			if (self.statusbar) {
22158 				items.add(self.statusbar.items());
22159 			}
22160 
22161 			items.each(function(ctrl) {
22162 				if (ctrl.settings.autofocus) {
22163 					focusCtrl = null;
22164 					return false;
22165 				}
22166 
22167 				if (ctrl.canFocus) {
22168 					focusCtrl = focusCtrl || ctrl;
22169 				}
22170 			});
22171 
22172 			if (focusCtrl) {
22173 				focusCtrl.focus();
22174 			}
22175 
22176 			return self;
22177 		},
22178 
22179 		/**
22180 		 * Replaces the specified child control with a new control.
22181 		 *
22182 		 * @method replace
22183 		 * @param {tinymce.ui.Control} oldItem Old item to be replaced.
22184 		 * @param {tinymce.ui.Control} newItem New item to be inserted.
22185 		 */
22186 		replace: function(oldItem, newItem) {
22187 			var ctrlElm, items = this.items(), i = items.length;
22188 
22189 			// Replace the item in collection
22190 			while (i--) {
22191 				if (items[i] === oldItem) {
22192 					items[i] = newItem;
22193 					break;
22194 				}
22195 			}
22196 
22197 			if (i >= 0) {
22198 				// Remove new item from DOM
22199 				ctrlElm = newItem.getEl();
22200 				if (ctrlElm) {
22201 					ctrlElm.parentNode.removeChild(ctrlElm);
22202 				}
22203 
22204 				// Remove old item from DOM
22205 				ctrlElm = oldItem.getEl();
22206 				if (ctrlElm) {
22207 					ctrlElm.parentNode.removeChild(ctrlElm);
22208 				}
22209 			}
22210 
22211 			// Adopt the item
22212 			newItem.parent(this);
22213 		},
22214 
22215 		/**
22216 		 * Creates the specified items. If any of the items is plain JSON style objects
22217 		 * it will convert these into real tinymce.ui.Control instances.
22218 		 *
22219 		 * @method create
22220 		 * @param {Array} items Array of items to convert into control instances.
22221 		 * @return {Array} Array with control instances.
22222 		 */
22223 		create: function(items) {
22224 			var self = this, settings, ctrlItems = [];
22225 
22226 			// Non array structure, then force it into an array
22227 			if (!Tools.isArray(items)) {
22228 				items = [items];
22229 			}
22230 
22231 			// Add default type to each child control
22232 			Tools.each(items, function(item) {
22233 				if (item) {
22234 					// Construct item if needed
22235 					if (!(item instanceof Control)) {
22236 						// Name only then convert it to an object
22237 						if (typeof(item) == "string") {
22238 							item = {type: item};
22239 						}
22240 
22241 						// Create control instance based on input settings and default settings
22242 						settings = Tools.extend({}, self.settings.defaults, item);
22243 						item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
22244 							(settings.defaults ? settings.defaults.type : null);
22245 						item = Factory.create(settings);
22246 					}
22247 
22248 					ctrlItems.push(item);
22249 				}
22250 			});
22251 
22252 			return ctrlItems;
22253 		},
22254 
22255 		/**
22256 		 * Renders new control instances.
22257 		 *
22258 		 * @private
22259 		 */
22260 		renderNew: function() {
22261 			var self = this;
22262 
22263 			// Render any new items
22264 			self.items().each(function(ctrl, index) {
22265 				var containerElm, fragment;
22266 
22267 				ctrl.parent(self);
22268 
22269 				if (!ctrl._rendered) {
22270 					containerElm = self.getEl('body');
22271 					fragment = DomUtils.createFragment(ctrl.renderHtml());
22272 
22273 					// Insert or append the item
22274 					if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
22275 						containerElm.insertBefore(fragment, containerElm.childNodes[index]);
22276 					} else {
22277 						containerElm.appendChild(fragment);
22278 					}
22279 
22280 					ctrl.postRender();
22281 				}
22282 			});
22283 
22284 			self._layout.applyClasses(self);
22285 			self._lastRect = null;
22286 
22287 			return self;
22288 		},
22289 
22290 		/**
22291 		 * Appends new instances to the current container.
22292 		 *
22293 		 * @method append
22294 		 * @param {Array/tinymce.ui.Collection} items Array if controls to append.
22295 		 * @return {tinymce.ui.Container} Current container instance.
22296 		 */
22297 		append: function(items) {
22298 			return this.add(items).renderNew();
22299 		},
22300 
22301 		/**
22302 		 * Prepends new instances to the current container.
22303 		 *
22304 		 * @method prepend
22305 		 * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
22306 		 * @return {tinymce.ui.Container} Current container instance.
22307 		 */
22308 		prepend: function(items) {
22309 			var self = this;
22310 
22311 			self.items().set(self.create(items).concat(self.items().toArray()));
22312 
22313 			return self.renderNew();
22314 		},
22315 
22316 		/**
22317 		 * Inserts an control at a specific index.
22318 		 *
22319 		 * @method insert
22320 		 * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
22321 		 * @param {Number} index Index to insert controls at.
22322 		 * @param {Boolean} [before=false] Inserts controls before the index.
22323 		 */
22324 		insert: function(items, index, before) {
22325 			var self = this, curItems, beforeItems, afterItems;
22326 
22327 			items = self.create(items);
22328 			curItems = self.items();
22329 
22330 			if (!before && index < curItems.length - 1) {
22331 				index += 1;
22332 			}
22333 
22334 			if (index >= 0 && index < curItems.length) {
22335 				beforeItems = curItems.slice(0, index).toArray();
22336 				afterItems = curItems.slice(index).toArray();
22337 				curItems.set(beforeItems.concat(items, afterItems));
22338 			}
22339 
22340 			return self.renderNew();
22341 		},
22342 
22343 		/**
22344 		 * Populates the form fields from the specified JSON data object.
22345 		 *
22346 		 * Control items in the form that matches the data will have it's value set.
22347 		 *
22348 		 * @method fromJSON
22349 		 * @param {Object} data JSON data object to set control values by.
22350 		 * @return {tinymce.ui.Container} Current form instance.
22351 		 */
22352 		fromJSON: function(data) {
22353 			var self = this;
22354 
22355 			for (var name in data) {
22356 				self.find('#' + name).value(data[name]);
22357 			}
22358 
22359 			return self;
22360 		},
22361 
22362 		/**
22363 		 * Serializes the form into a JSON object by getting all items
22364 		 * that has a name and a value.
22365 		 *
22366 		 * @method toJSON
22367 		 * @return {Object} JSON object with form data.
22368 		 */
22369 		toJSON: function() {
22370 			var self = this, data = {};
22371 
22372 			self.find('*').each(function(ctrl) {
22373 				var name = ctrl.name(), value = ctrl.value();
22374 
22375 				if (name && typeof(value) != "undefined") {
22376 					data[name] = value;
22377 				}
22378 			});
22379 
22380 			return data;
22381 		},
22382 
22383 		preRender: function() {
22384 		},
22385 
22386 		/**
22387 		 * Renders the control as a HTML string.
22388 		 *
22389 		 * @method renderHtml
22390 		 * @return {String} HTML representing the control.
22391 		 */
22392 		renderHtml: function() {
22393 			var self = this, layout = self._layout, role = this.settings.role;
22394 
22395 			self.preRender();
22396 			layout.preRender(self);
22397 
22398 			return (
22399 				'<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
22400 					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
22401 						(self.settings.html || '') + layout.renderHtml(self) +
22402 					'</div>' +
22403 				'</div>'
22404 			);
22405 		},
22406 
22407 		/**
22408 		 * Post render method. Called after the control has been rendered to the target.
22409 		 *
22410 		 * @method postRender
22411 		 * @return {tinymce.ui.Container} Current combobox instance.
22412 		 */
22413 		postRender: function() {
22414 			var self = this, box;
22415 
22416 			self.items().exec('postRender');
22417 			self._super();
22418 
22419 			self._layout.postRender(self);
22420 			self._rendered = true;
22421 
22422 			if (self.settings.style) {
22423 				DomUtils.css(self.getEl(), self.settings.style);
22424 			}
22425 
22426 			if (self.settings.border) {
22427 				box = self.borderBox();
22428 				DomUtils.css(self.getEl(), {
22429 					'border-top-width': box.top,
22430 					'border-right-width': box.right,
22431 					'border-bottom-width': box.bottom,
22432 					'border-left-width': box.left
22433 				});
22434 			}
22435 
22436 			if (!self.parent()) {
22437 				self.keyboardNav = new KeyboardNavigation({
22438 					root: self
22439 				});
22440 			}
22441 
22442 			return self;
22443 		},
22444 
22445 		/**
22446 		 * Initializes the current controls layout rect.
22447 		 * This will be executed by the layout managers to determine the
22448 		 * default minWidth/minHeight etc.
22449 		 *
22450 		 * @method initLayoutRect
22451 		 * @return {Object} Layout rect instance.
22452 		 */
22453 		initLayoutRect: function() {
22454 			var self = this, layoutRect = self._super();
22455 
22456 			// Recalc container size by asking layout manager
22457 			self._layout.recalc(self);
22458 
22459 			return layoutRect;
22460 		},
22461 
22462 		/**
22463 		 * Recalculates the positions of the controls in the current container.
22464 		 * This is invoked by the reflow method and shouldn't be called directly.
22465 		 *
22466 		 * @method recalc
22467 		 */
22468 		recalc: function() {
22469 			var self = this, rect = self._layoutRect, lastRect = self._lastRect;
22470 
22471 			if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
22472 				self._layout.recalc(self);
22473 				rect = self.layoutRect();
22474 				self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
22475 				return true;
22476 			}
22477 		},
22478 
22479 		/**
22480 		 * Reflows the current container and it's children and possible parents.
22481 		 * This should be used after you for example append children to the current control so
22482 		 * that the layout managers know that they need to reposition everything.
22483 		 *
22484 		 * @example
22485 		 * container.append({type: 'button', text: 'My button'}).reflow();
22486 		 *
22487 		 * @method reflow
22488 		 * @return {tinymce.ui.Container} Current container instance.
22489 		 */
22490 		reflow: function() {
22491 			var i;
22492 
22493 			if (this.visible()) {
22494 				Control.repaintControls = [];
22495 				Control.repaintControls.map = {};
22496 
22497 				this.recalc();
22498 				i = Control.repaintControls.length;
22499 
22500 				while (i--) {
22501 					Control.repaintControls[i].repaint();
22502 				}
22503 
22504 				// TODO: Fix me!
22505 				if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
22506 					this.repaint();
22507 				}
22508 
22509 				Control.repaintControls = [];
22510 			}
22511 
22512 			return this;
22513 		}
22514 	});
22515 });
22516 
22517 // Included from: js/tinymce/classes/ui/DragHelper.js
22518 
22519 /**
22520  * DragHelper.js
22521  *
22522  * Copyright, Moxiecode Systems AB
22523  * Released under LGPL License.
22524  *
22525  * License: http://www.tinymce.com/license
22526  * Contributing: http://www.tinymce.com/contributing
22527  */
22528 
22529 /**
22530  * Drag/drop helper class.
22531  *
22532  * @example
22533  * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
22534  *     start: function(evt) {
22535  *     },
22536  *
22537  *     drag: function(evt) {
22538  *     },
22539  *
22540  *     end: function(evt) {
22541  *     }
22542  * });
22543  *
22544  * @class tinymce.ui.DragHelper
22545  */
22546 define("tinymce/ui/DragHelper", [
22547 	"tinymce/ui/DomUtils"
22548 ], function(DomUtils) {
22549 	"use strict";
22550 
22551 	function getDocumentSize() {
22552 		var doc = document, documentElement, body, scrollWidth, clientWidth;
22553 		var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
22554 
22555 		documentElement = doc.documentElement;
22556 		body = doc.body;
22557 
22558 		scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
22559 		clientWidth = max(documentElement.clientWidth, body.clientWidth);
22560 		offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
22561 
22562 		scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
22563 		clientHeight = max(documentElement.clientHeight, body.clientHeight);
22564 		offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
22565 
22566 		return {
22567 			width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
22568 			height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
22569 		};
22570 	}
22571 
22572 	return function(id, settings) {
22573 		var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;
22574 
22575 		settings = settings || {};
22576 
22577 		function getHandleElm() {
22578 			return doc.getElementById(settings.handle || id);
22579 		}
22580 
22581 		start = function(e) {
22582 			var docSize = getDocumentSize(), handleElm, cursor;
22583 
22584 			e.preventDefault();
22585 			downButton = e.button;
22586 			handleElm = getHandleElm();
22587 			startX = e.screenX;
22588 			startY = e.screenY;
22589 
22590 			// Grab cursor from handle
22591 			if (window.getComputedStyle) {
22592 				cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
22593 			} else {
22594 				cursor = handleElm.runtimeStyle.cursor;
22595 			}
22596 
22597 			// Create event overlay and add it to document
22598 			eventOverlayElm = doc.createElement('div');
22599 			DomUtils.css(eventOverlayElm, {
22600 				position: "absolute",
22601 				top: 0, left: 0,
22602 				width: docSize.width,
22603 				height: docSize.height,
22604 				zIndex: 0x7FFFFFFF,
22605 				opacity: 0.0001,
22606 				cursor: cursor
22607 			});
22608 
22609 			doc.body.appendChild(eventOverlayElm);
22610 
22611 			// Bind mouse events
22612 			DomUtils.on(doc, 'mousemove', drag);
22613 			DomUtils.on(doc, 'mouseup', stop);
22614 
22615 			// Begin drag
22616 			settings.start(e);
22617 		};
22618 
22619 		drag = function(e) {
22620 			if (e.button !== downButton) {
22621 				return stop(e);
22622 			}
22623 
22624 			e.deltaX = e.screenX - startX;
22625 			e.deltaY = e.screenY - startY;
22626 
22627 			e.preventDefault();
22628 			settings.drag(e);
22629 		};
22630 
22631 		stop = function(e) {
22632 			DomUtils.off(doc, 'mousemove', drag);
22633 			DomUtils.off(doc, 'mouseup', stop);
22634 
22635 			eventOverlayElm.parentNode.removeChild(eventOverlayElm);
22636 
22637 			if (settings.stop) {
22638 				settings.stop(e);
22639 			}
22640 		};
22641 
22642 		/**
22643 		 * Destroys the drag/drop helper instance.
22644 		 *
22645 		 * @method destroy
22646 		 */
22647 		this.destroy = function() {
22648 			DomUtils.off(getHandleElm());
22649 		};
22650 
22651 		DomUtils.on(getHandleElm(), 'mousedown', start);
22652 	};
22653 });
22654 
22655 // Included from: js/tinymce/classes/ui/Scrollable.js
22656 
22657 /**
22658  * Scrollable.js
22659  *
22660  * Copyright, Moxiecode Systems AB
22661  * Released under LGPL License.
22662  *
22663  * License: http://www.tinymce.com/license
22664  * Contributing: http://www.tinymce.com/contributing
22665  */
22666 
22667 /**
22668  * This mixin makes controls scrollable using custom scrollbars.
22669  *
22670  * @-x-less Scrollable.less
22671  * @mixin tinymce.ui.Scrollable
22672  */
22673 define("tinymce/ui/Scrollable", [
22674 	"tinymce/ui/DomUtils",
22675 	"tinymce/ui/DragHelper"
22676 ], function(DomUtils, DragHelper) {
22677 	"use strict";
22678 
22679 	return {
22680 		init: function() {
22681 			var self = this;
22682 			self.on('repaint', self.renderScroll);
22683 		},
22684 
22685 		renderScroll: function() {
22686 			var self = this, margin = 2;
22687 
22688 			function repaintScroll() {
22689 				var hasScrollH, hasScrollV, bodyElm;
22690 
22691 				function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
22692 					var containerElm, scrollBarElm, scrollThumbElm;
22693 					var containerSize, scrollSize, ratio, rect;
22694 					var posNameLower, sizeNameLower;
22695 
22696 					scrollBarElm = self.getEl('scroll' + axisName);
22697 					if (scrollBarElm) {
22698 						posNameLower = posName.toLowerCase();
22699 						sizeNameLower = sizeName.toLowerCase();
22700 
22701 						if (self.getEl('absend')) {
22702 							DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
22703 						}
22704 
22705 						if (!hasScroll) {
22706 							DomUtils.css(scrollBarElm, 'display', 'none');
22707 							return;
22708 						}
22709 
22710 						DomUtils.css(scrollBarElm, 'display', 'block');
22711 						containerElm = self.getEl('body');
22712 						scrollThumbElm = self.getEl('scroll' + axisName + "t");
22713 						containerSize = containerElm["client" + sizeName] - (margin * 2);
22714 						containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
22715 						scrollSize = containerElm["scroll" + sizeName];
22716 						ratio = containerSize / scrollSize;
22717 
22718 						rect = {};
22719 						rect[posNameLower] = containerElm["offset" + posName] + margin;
22720 						rect[sizeNameLower] = containerSize;
22721 						DomUtils.css(scrollBarElm, rect);
22722 
22723 						rect = {};
22724 						rect[posNameLower] = containerElm["scroll" + posName] * ratio;
22725 						rect[sizeNameLower] = containerSize * ratio;
22726 						DomUtils.css(scrollThumbElm, rect);
22727 					}
22728 				}
22729 
22730 				bodyElm = self.getEl('body');
22731 				hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
22732 				hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
22733 
22734 				repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
22735 				repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
22736 			}
22737 
22738 			function addScroll() {
22739 				function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
22740 					var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
22741 
22742 					self.getEl().appendChild(DomUtils.createFragment(
22743 						'<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
22744 							'<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
22745 						'</div>'
22746 					));
22747 
22748 					self.draghelper = new DragHelper(axisId + 't', {
22749 						start: function() {
22750 							scrollStart = self.getEl('body')["scroll" + posName];
22751 							DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
22752 						},
22753 
22754 						drag: function(e) {
22755 							var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
22756 
22757 							hasScrollH = layoutRect.contentW > layoutRect.innerW;
22758 							hasScrollV = layoutRect.contentH > layoutRect.innerH;
22759 							containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
22760 							containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
22761 
22762 							ratio = containerSize / self.getEl('body')["scroll" + sizeName];
22763 							self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
22764 						},
22765 
22766 						stop: function() {
22767 							DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
22768 						}
22769 					});
22770 /*
22771 					self.on('click', function(e) {
22772 						if (e.target.id == self._id + '-scrollv') {
22773 
22774 						}
22775 					});*/
22776 				}
22777 
22778 				self.addClass('scroll');
22779 
22780 				addScrollAxis("v", "Top", "Height", "Y", "Width");
22781 				addScrollAxis("h", "Left", "Width", "X", "Height");
22782 			}
22783 
22784 			if (self.settings.autoScroll) {
22785 				if (!self._hasScroll) {
22786 					self._hasScroll = true;
22787 					addScroll();
22788 
22789 					self.on('wheel', function(e) {
22790 						var bodyEl = self.getEl('body');
22791 
22792 						bodyEl.scrollLeft += (e.deltaX || 0) * 10;
22793 						bodyEl.scrollTop += e.deltaY * 10;
22794 
22795 						repaintScroll();
22796 					});
22797 
22798 					DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
22799 				}
22800 
22801 				repaintScroll();
22802 			}
22803 		}
22804 	};
22805 });
22806 
22807 // Included from: js/tinymce/classes/ui/Panel.js
22808 
22809 /**
22810  * Panel.js
22811  *
22812  * Copyright, Moxiecode Systems AB
22813  * Released under LGPL License.
22814  *
22815  * License: http://www.tinymce.com/license
22816  * Contributing: http://www.tinymce.com/contributing
22817  */
22818 
22819 /**
22820  * Creates a new panel.
22821  *
22822  * @-x-less Panel.less
22823  * @class tinymce.ui.Panel
22824  * @extends tinymce.ui.Container
22825  * @mixes tinymce.ui.Scrollable
22826  */
22827 define("tinymce/ui/Panel", [
22828 	"tinymce/ui/Container",
22829 	"tinymce/ui/Scrollable"
22830 ], function(Container, Scrollable) {
22831 	"use strict";
22832 
22833 	return Container.extend({
22834 		Defaults: {
22835 			layout: 'fit',
22836 			containerCls: 'panel'
22837 		},
22838 
22839 		Mixins: [Scrollable],
22840 
22841 		/**
22842 		 * Renders the control as a HTML string.
22843 		 *
22844 		 * @method renderHtml
22845 		 * @return {String} HTML representing the control.
22846 		 */
22847 		renderHtml: function() {
22848 			var self = this, layout = self._layout, innerHtml = self.settings.html;
22849 
22850 			self.preRender();
22851 			layout.preRender(self);
22852 
22853 			if (typeof(innerHtml) == "undefined") {
22854 				innerHtml = (
22855 					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
22856 						layout.renderHtml(self) +
22857 					'</div>'
22858 				);
22859 			} else {
22860 				if (typeof(innerHtml) == 'function') {
22861 					innerHtml = innerHtml.call(self);
22862 				}
22863 
22864 				self._hasBody = false;
22865 			}
22866 
22867 			return (
22868 				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
22869 					(self._preBodyHtml || '') +
22870 					innerHtml +
22871 				'</div>'
22872 			);
22873 		}
22874 	});
22875 });
22876 
22877 // Included from: js/tinymce/classes/ui/Movable.js
22878 
22879 /**
22880  * Movable.js
22881  *
22882  * Copyright, Moxiecode Systems AB
22883  * Released under LGPL License.
22884  *
22885  * License: http://www.tinymce.com/license
22886  * Contributing: http://www.tinymce.com/contributing
22887  */
22888 
22889 /**
22890  * Movable mixin. Makes controls movable absolute and relative to other elements.
22891  *
22892  * @mixin tinymce.ui.Movable
22893  */
22894 define("tinymce/ui/Movable", [
22895 	"tinymce/ui/DomUtils"
22896 ], function(DomUtils) {
22897 	"use strict";
22898 
22899 	function calculateRelativePosition(ctrl, targetElm, rel) {
22900 		var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
22901 
22902 		viewport = DomUtils.getViewPort();
22903 
22904 		// Get pos of target
22905 		pos = DomUtils.getPos(targetElm);
22906 		x = pos.x;
22907 		y = pos.y;
22908 
22909 		if (ctrl._fixed && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') {
22910 			x -= viewport.x;
22911 			y -= viewport.y;
22912 		}
22913 
22914 		// Get size of self
22915 		ctrlElm = ctrl.getEl();
22916 		size = DomUtils.getSize(ctrlElm);
22917 		selfW = size.width;
22918 		selfH = size.height;
22919 
22920 		// Get size of target
22921 		size = DomUtils.getSize(targetElm);
22922 		targetW = size.width;
22923 		targetH = size.height;
22924 
22925 		// Parse align string
22926 		rel = (rel || '').split('');
22927 
22928 		// Target corners
22929 		if (rel[0] === 'b') {
22930 			y += targetH;
22931 		}
22932 
22933 		if (rel[1] === 'r') {
22934 			x += targetW;
22935 		}
22936 
22937 		if (rel[0] === 'c') {
22938 			y += Math.round(targetH / 2);
22939 		}
22940 
22941 		if (rel[1] === 'c') {
22942 			x += Math.round(targetW / 2);
22943 		}
22944 
22945 		// Self corners
22946 		if (rel[3] === 'b') {
22947 			y -= selfH;
22948 		}
22949 
22950 		if (rel[4] === 'r') {
22951 			x -= selfW;
22952 		}
22953 
22954 		if (rel[3] === 'c') {
22955 			y -= Math.round(selfH / 2);
22956 		}
22957 
22958 		if (rel[4] === 'c') {
22959 			x -= Math.round(selfW / 2);
22960 		}
22961 
22962 		return {
22963 			x: x,
22964 			y: y,
22965 			w: selfW,
22966 			h: selfH
22967 		};
22968 	}
22969 
22970 	return {
22971 		/**
22972 		 * Tests various positions to get the most suitable one.
22973 		 *
22974 		 * @method testMoveRel
22975 		 * @param {DOMElement} elm Element to position against.
22976 		 * @param {Array} rels Array with relative positions.
22977 		 * @return {String} Best suitable relative position.
22978 		 */
22979 		testMoveRel: function(elm, rels) {
22980 			var viewPortRect = DomUtils.getViewPort();
22981 
22982 			for (var i = 0; i < rels.length; i++) {
22983 				var pos = calculateRelativePosition(this, elm, rels[i]);
22984 
22985 				if (this._fixed) {
22986 					if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
22987 						return rels[i];
22988 					}
22989 				} else {
22990 					if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
22991 						pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
22992 						return rels[i];
22993 					}
22994 				}
22995 			}
22996 
22997 			return rels[0];
22998 		},
22999 
23000 		/**
23001 		 * Move relative to the specified element.
23002 		 *
23003 		 * @method moveRel
23004 		 * @param {Element} elm Element to move relative to.
23005 		 * @param {String} rel Relative mode. For example: br-tl.
23006 		 * @return {tinymce.ui.Control} Current control instance.
23007 		 */
23008 		moveRel: function(elm, rel) {
23009 			if (typeof(rel) != 'string') {
23010 				rel = this.testMoveRel(elm, rel);
23011 			}
23012 
23013 			var pos = calculateRelativePosition(this, elm, rel);
23014 			return this.moveTo(pos.x, pos.y);
23015 		},
23016 
23017 		/**
23018 		 * Move by a relative x, y values.
23019 		 *
23020 		 * @method moveBy
23021 		 * @param {Number} dx Relative x position.
23022 		 * @param {Number} dy Relative y position.
23023 		 * @return {tinymce.ui.Control} Current control instance.
23024 		 */
23025 		moveBy: function(dx, dy) {
23026 			var self = this, rect = self.layoutRect();
23027 
23028 			self.moveTo(rect.x + dx, rect.y + dy);
23029 
23030 			return self;
23031 		},
23032 
23033 		/**
23034 		 * Move to absolute position.
23035 		 *
23036 		 * @method moveTo
23037 		 * @param {Number} x Absolute x position.
23038 		 * @param {Number} y Absolute y position.
23039 		 * @return {tinymce.ui.Control} Current control instance.
23040 		 */
23041 		moveTo: function(x, y) {
23042 			var self = this;
23043 
23044 			// TODO: Move this to some global class
23045 			function contrain(value, max, size) {
23046 				if (value < 0) {
23047 					return 0;
23048 				}
23049 
23050 				if (value + size > max) {
23051 					value = max - size;
23052 					return value < 0 ? 0 : value;
23053 				}
23054 
23055 				return value;
23056 			}
23057 
23058 			if (self.settings.constrainToViewport) {
23059 				var viewPortRect = DomUtils.getViewPort(window);
23060 				var layoutRect = self.layoutRect();
23061 
23062 				x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
23063 				y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
23064 			}
23065 
23066 			if (self._rendered) {
23067 				self.layoutRect({x: x, y: y}).repaint();
23068 			} else {
23069 				self.settings.x = x;
23070 				self.settings.y = y;
23071 			}
23072 
23073 			self.fire('move', {x: x, y: y});
23074 
23075 			return self;
23076 		}
23077 	};
23078 });
23079 
23080 // Included from: js/tinymce/classes/ui/Resizable.js
23081 
23082 /**
23083  * Resizable.js
23084  *
23085  * Copyright, Moxiecode Systems AB
23086  * Released under LGPL License.
23087  *
23088  * License: http://www.tinymce.com/license
23089  * Contributing: http://www.tinymce.com/contributing
23090  */
23091 
23092 /**
23093  * Resizable mixin. Enables controls to be resized.
23094  *
23095  * @mixin tinymce.ui.Resizable
23096  */
23097 define("tinymce/ui/Resizable", [
23098 	"tinymce/ui/DomUtils"
23099 ], function(DomUtils) {
23100 	"use strict";
23101 
23102 	return {
23103 		/**
23104 		 * Resizes the control to contents.
23105 		 *
23106 		 * @method resizeToContent
23107 		 */
23108 		resizeToContent: function() {
23109 			this._layoutRect.autoResize = true;
23110 			this._lastRect = null;
23111 			this.reflow();
23112 		},
23113 
23114 		/**
23115 		 * Resizes the control to a specific width/height.
23116 		 *
23117 		 * @method resizeTo
23118 		 * @param {Number} w Control width.
23119 		 * @param {Number} h Control height.
23120 		 * @return {tinymce.ui.Control} Current control instance.
23121 		 */
23122 		resizeTo: function(w, h) {
23123 			// TODO: Fix hack
23124 			if (w <= 1 || h <= 1) {
23125 				var rect = DomUtils.getWindowSize();
23126 
23127 				w = w <= 1 ? w * rect.w : w;
23128 				h = h <= 1 ? h * rect.h : h;
23129 			}
23130 
23131 			this._layoutRect.autoResize = false;
23132 			return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
23133 		},
23134 
23135 		/**
23136 		 * Resizes the control to a specific relative width/height.
23137 		 *
23138 		 * @method resizeBy
23139 		 * @param {Number} dw Relative control width.
23140 		 * @param {Number} dh Relative control height.
23141 		 * @return {tinymce.ui.Control} Current control instance.
23142 		 */
23143 		resizeBy: function(dw, dh) {
23144 			var self = this, rect = self.layoutRect();
23145 
23146 			return self.resizeTo(rect.w + dw, rect.h + dh);
23147 		}
23148 	};
23149 });
23150 
23151 // Included from: js/tinymce/classes/ui/FloatPanel.js
23152 
23153 /**
23154  * FloatPanel.js
23155  *
23156  * Copyright, Moxiecode Systems AB
23157  * Released under LGPL License.
23158  *
23159  * License: http://www.tinymce.com/license
23160  * Contributing: http://www.tinymce.com/contributing
23161  */
23162 
23163 /**
23164  * This class creates a floating panel.
23165  *
23166  * @-x-less FloatPanel.less
23167  * @class tinymce.ui.FloatPanel
23168  * @extends tinymce.ui.Panel
23169  * @mixes tinymce.ui.Movable
23170  * @mixes tinymce.ui.Resizable
23171  */
23172 define("tinymce/ui/FloatPanel", [
23173 	"tinymce/ui/Panel",
23174 	"tinymce/ui/Movable",
23175 	"tinymce/ui/Resizable",
23176 	"tinymce/ui/DomUtils"
23177 ], function(Panel, Movable, Resizable, DomUtils) {
23178 	"use strict";
23179 
23180 	var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
23181 	var zOrder = [], hasModal;
23182 
23183 	function bindDocumentClickHandler() {
23184 		function isChildOf(ctrl, parent) {
23185 			while (ctrl) {
23186 				if (ctrl == parent) {
23187 					return true;
23188 				}
23189 
23190 				ctrl = ctrl.parent();
23191 			}
23192 		}
23193 
23194 		if (!documentClickHandler) {
23195 			documentClickHandler = function(e) {
23196 				// Gecko fires click event and in the wrong order on Mac so lets normalize
23197 				if (e.button == 2) {
23198 					return;
23199 				}
23200 
23201 				// Hide any float panel when a click is out side that float panel and the
23202 				// float panels direct parent for example a click on a menu button
23203 				var i = visiblePanels.length;
23204 				while (i--) {
23205 					var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
23206 
23207 					if (panel.settings.autohide) {
23208 						if (clickCtrl) {
23209 							if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
23210 								continue;
23211 							}
23212 						}
23213 
23214 						e = panel.fire('autohide', {target: e.target});
23215 						if (!e.isDefaultPrevented()) {
23216 							panel.hide();
23217 						}
23218 					}
23219 				}
23220 			};
23221 
23222 			DomUtils.on(document, 'click', documentClickHandler);
23223 		}
23224 	}
23225 
23226 	function bindDocumentScrollHandler() {
23227 		if (!documentScrollHandler) {
23228 			documentScrollHandler = function() {
23229 				var i;
23230 
23231 				i = visiblePanels.length;
23232 				while (i--) {
23233 					repositionPanel(visiblePanels[i]);
23234 				}
23235 			};
23236 
23237 			DomUtils.on(window, 'scroll', documentScrollHandler);
23238 		}
23239 	}
23240 
23241 	function bindWindowResizeHandler() {
23242 		if (!windowResizeHandler) {
23243 			var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight;
23244 
23245 			windowResizeHandler = function() {
23246 				// Workaround for #7065 IE 7 fires resize events event though the window wasn't resized
23247 				if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) {
23248 					clientWidth = docElm.clientWidth;
23249 					clientHeight = docElm.clientHeight;
23250 					FloatPanel.hideAll();
23251 				}
23252 			};
23253 
23254 			DomUtils.on(window, 'resize', windowResizeHandler);
23255 		}
23256 	}
23257 
23258 	/**
23259 	 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
23260 	 * also reposition all child panels of the current panel.
23261 	 */
23262 	function repositionPanel(panel) {
23263 		var scrollY = DomUtils.getViewPort().y;
23264 
23265 		function toggleFixedChildPanels(fixed, deltaY) {
23266 			var parent;
23267 
23268 			for (var i = 0; i < visiblePanels.length; i++) {
23269 				if (visiblePanels[i] != panel) {
23270 					parent = visiblePanels[i].parent();
23271 
23272 					while (parent && (parent = parent.parent())) {
23273 						if (parent == panel) {
23274 							visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
23275 						}
23276 					}
23277 				}
23278 			}
23279 		}
23280 
23281 		if (panel.settings.autofix) {
23282 			if (!panel._fixed) {
23283 				panel._autoFixY = panel.layoutRect().y;
23284 
23285 				if (panel._autoFixY < scrollY) {
23286 					panel.fixed(true).layoutRect({y: 0}).repaint();
23287 					toggleFixedChildPanels(true, scrollY - panel._autoFixY);
23288 				}
23289 			} else {
23290 				if (panel._autoFixY > scrollY) {
23291 					panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
23292 					toggleFixedChildPanels(false, panel._autoFixY - scrollY);
23293 				}
23294 			}
23295 		}
23296 	}
23297 
23298 	function addRemove(add, ctrl) {
23299 		var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
23300 
23301 		if (add) {
23302 			zOrder.push(ctrl);
23303 		} else {
23304 			i = zOrder.length;
23305 
23306 			while (i--) {
23307 				if (zOrder[i] === ctrl) {
23308 					zOrder.splice(i, 1);
23309 				}
23310 			}
23311 		}
23312 
23313 		if (zOrder.length) {
23314 			for (i = 0; i < zOrder.length; i++) {
23315 				if (zOrder[i].modal) {
23316 					zIndex++;
23317 					topModal = zOrder[i];
23318 				}
23319 
23320 				zOrder[i].getEl().style.zIndex = zIndex;
23321 				zOrder[i].zIndex = zIndex;
23322 				zIndex++;
23323 			}
23324 		}
23325 
23326 		var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block');
23327 
23328 		if (topModal) {
23329 			DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
23330 		} else if (modalBlockEl) {
23331 			modalBlockEl.parentNode.removeChild(modalBlockEl);
23332 			hasModal = false;
23333 		}
23334 
23335 		FloatPanel.currentZIndex = zIndex;
23336 	}
23337 
23338 	var FloatPanel = Panel.extend({
23339 		Mixins: [Movable, Resizable],
23340 		Defaults: {
23341 			wrapFocus: true
23342 		},
23343 
23344 		/**
23345 		 * Constructs a new control instance with the specified settings.
23346 		 *
23347 		 * @constructor
23348 		 * @param {Object} settings Name/value object with settings.
23349 		 * @setting {Boolean} autohide Automatically hide the panel.
23350 		 */
23351 		init: function(settings) {
23352 			var self = this;
23353 
23354 			self._super(settings);
23355 			self._eventsRoot = self;
23356 
23357 			self.addClass('floatpanel');
23358 
23359 			// Hide floatpanes on click out side the root button
23360 			if (settings.autohide) {
23361 				bindDocumentClickHandler();
23362 				bindWindowResizeHandler();
23363 				visiblePanels.push(self);
23364 			}
23365 
23366 			if (settings.autofix) {
23367 				bindDocumentScrollHandler();
23368 
23369 				self.on('move', function() {
23370 					repositionPanel(this);
23371 				});
23372 			}
23373 
23374 			self.on('postrender show', function(e) {
23375 				if (e.control == self) {
23376 					var modalBlockEl, prefix = self.classPrefix;
23377 
23378 					if (self.modal && !hasModal) {
23379 						modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
23380 							prefix + 'reset ' + prefix + 'fade"></div>');
23381 						modalBlockEl = modalBlockEl.firstChild;
23382 
23383 						self.getContainerElm().appendChild(modalBlockEl);
23384 
23385 						setTimeout(function() {
23386 							DomUtils.addClass(modalBlockEl, prefix + 'in');
23387 							DomUtils.addClass(self.getEl(), prefix + 'in');
23388 						}, 0);
23389 
23390 						hasModal = true;
23391 					}
23392 
23393 					addRemove(true, self);
23394 				}
23395 			});
23396 
23397 			self.on('show', function() {
23398 				self.parents().each(function(ctrl) {
23399 					if (ctrl._fixed) {
23400 						self.fixed(true);
23401 						return false;
23402 					}
23403 				});
23404 			});
23405 
23406 			if (settings.popover) {
23407 				self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
23408 				self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start');
23409 			}
23410 		},
23411 
23412 		fixed: function(state) {
23413 			var self = this;
23414 
23415 			if (self._fixed != state) {
23416 				if (self._rendered) {
23417 					var viewport = DomUtils.getViewPort();
23418 
23419 					if (state) {
23420 						self.layoutRect().y -= viewport.y;
23421 					} else {
23422 						self.layoutRect().y += viewport.y;
23423 					}
23424 				}
23425 
23426 				self.toggleClass('fixed', state);
23427 				self._fixed = state;
23428 			}
23429 
23430 			return self;
23431 		},
23432 
23433 		/**
23434 		 * Shows the current float panel.
23435 		 *
23436 		 * @method show
23437 		 * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
23438 		 */
23439 		show: function() {
23440 			var self = this, i, state = self._super();
23441 
23442 			i = visiblePanels.length;
23443 			while (i--) {
23444 				if (visiblePanels[i] === self) {
23445 					break;
23446 				}
23447 			}
23448 
23449 			if (i === -1) {
23450 				visiblePanels.push(self);
23451 			}
23452 
23453 			return state;
23454 		},
23455 
23456 		/**
23457 		 * Hides the current float panel.
23458 		 *
23459 		 * @method hide
23460 		 * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
23461 		 */
23462 		hide: function() {
23463 			removeVisiblePanel(this);
23464 			addRemove(false, this);
23465 
23466 			return this._super();
23467 		},
23468 
23469 		/**
23470 		 * Hide all visible float panels with he autohide setting enabled. This is for
23471 		 * manually hiding floating menus or panels.
23472 		 *
23473 		 * @method hideAll
23474 		 */
23475 		hideAll: function() {
23476 			FloatPanel.hideAll();
23477 		},
23478 
23479 		/**
23480 		 * Closes the float panel. This will remove the float panel from page and fire the close event.
23481 		 *
23482 		 * @method close
23483 		 */
23484 		close: function() {
23485 			var self = this;
23486 
23487 			if (!self.fire('close').isDefaultPrevented()) {
23488 				self.remove();
23489 				addRemove(false, self);
23490 			}
23491 
23492 			return self;
23493 		},
23494 
23495 		/**
23496 		 * Removes the float panel from page.
23497 		 *
23498 		 * @method remove
23499 		 */
23500 		remove: function() {
23501 			removeVisiblePanel(this);
23502 			this._super();
23503 		},
23504 
23505 		postRender: function() {
23506 			var self = this;
23507 
23508 			if (self.settings.bodyRole) {
23509 				this.getEl('body').setAttribute('role', self.settings.bodyRole);
23510 			}
23511 
23512 			return self._super();
23513 		}
23514 	});
23515 
23516 	/**
23517 	 * Hide all visible float panels with he autohide setting enabled. This is for
23518 	 * manually hiding floating menus or panels.
23519 	 *
23520 	 * @static
23521 	 * @method hideAll
23522 	 */
23523 	FloatPanel.hideAll = function() {
23524 		var i = visiblePanels.length;
23525 
23526 		while (i--) {
23527 			var panel = visiblePanels[i];
23528 
23529 			if (panel && panel.settings.autohide) {
23530 				panel.hide();
23531 				visiblePanels.splice(i, 1);
23532 			}
23533 		}
23534 	};
23535 
23536 	function removeVisiblePanel(panel) {
23537 		var i;
23538 
23539 		i = visiblePanels.length;
23540 		while (i--) {
23541 			if (visiblePanels[i] === panel) {
23542 				visiblePanels.splice(i, 1);
23543 			}
23544 		}
23545 
23546 		i = zOrder.length;
23547 		while (i--) {
23548 			if (zOrder[i] === panel) {
23549 				zOrder.splice(i, 1);
23550 			}
23551 		}
23552 	}
23553 
23554 	return FloatPanel;
23555 });
23556 
23557 // Included from: js/tinymce/classes/ui/Window.js
23558 
23559 /**
23560  * Window.js
23561  *
23562  * Copyright, Moxiecode Systems AB
23563  * Released under LGPL License.
23564  *
23565  * License: http://www.tinymce.com/license
23566  * Contributing: http://www.tinymce.com/contributing
23567  */
23568 
23569 /**
23570  * Creates a new window.
23571  *
23572  * @-x-less Window.less
23573  * @class tinymce.ui.Window
23574  * @extends tinymce.ui.FloatPanel
23575  */
23576 define("tinymce/ui/Window", [
23577 	"tinymce/ui/FloatPanel",
23578 	"tinymce/ui/Panel",
23579 	"tinymce/ui/DomUtils",
23580 	"tinymce/ui/DragHelper"
23581 ], function(FloatPanel, Panel, DomUtils, DragHelper) {
23582 	"use strict";
23583 
23584 	var Window = FloatPanel.extend({
23585 		modal: true,
23586 
23587 		Defaults: {
23588 			border: 1,
23589 			layout: 'flex',
23590 			containerCls: 'panel',
23591 			role: 'dialog',
23592 			ariaRoot: true,
23593 			callbacks: {
23594 				submit: function() {
23595 					this.fire('submit', {data: this.toJSON()});
23596 				},
23597 
23598 				close: function() {
23599 					this.close();
23600 				}
23601 			}
23602 		},
23603 
23604 		/**
23605 		 * Constructs a instance with the specified settings.
23606 		 *
23607 		 * @constructor
23608 		 * @param {Object} settings Name/value object with settings.
23609 		 */
23610 		init: function(settings) {
23611 			var self = this;
23612 
23613 			self._super(settings);
23614 
23615 			if (self.isRtl()) {
23616 				self.addClass('rtl');
23617 			}
23618 
23619 			self.addClass('window');
23620 			self._fixed = true;
23621 
23622 			// Create statusbar
23623 			if (settings.buttons) {
23624 				self.statusbar = new Panel({
23625 					layout: 'flex',
23626 					border: '1 0 0 0',
23627 					spacing: 3,
23628 					padding: 10,
23629 					align: 'center',
23630 					pack: self.isRtl() ? 'start' : 'end',
23631 					defaults: {
23632 						type: 'button'
23633 					},
23634 					items: settings.buttons
23635 				});
23636 
23637 				self.statusbar.addClass('foot');
23638 				self.statusbar.parent(self);
23639 			}
23640 
23641 			self.on('click', function(e) {
23642 				if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
23643 					self.close();
23644 				}
23645 			});
23646 
23647 			self.on('cancel', function(e) {
23648 				e.preventDefault();
23649 				e.stopPropagation();
23650 				self.close();
23651 			});
23652 
23653 			self.aria('describedby', self.describedBy || self._id + '-title');
23654 			self.aria('label', settings.title);
23655 			self._fullscreen = false;
23656 		},
23657 
23658 		/**
23659 		 * Recalculates the positions of the controls in the current container.
23660 		 * This is invoked by the reflow method and shouldn't be called directly.
23661 		 *
23662 		 * @method recalc
23663 		 */
23664 		recalc: function() {
23665 			var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
23666 
23667 			if (self._fullscreen) {
23668 				self.layoutRect(DomUtils.getWindowSize());
23669 				self.layoutRect().contentH = self.layoutRect().innerH;
23670 			}
23671 
23672 			self._super();
23673 
23674 			layoutRect = self.layoutRect();
23675 
23676 			// Resize window based on title width
23677 			if (self.settings.title && !self._fullscreen) {
23678 				width = layoutRect.headerW;
23679 				if (width > layoutRect.w) {
23680 					x = layoutRect.x - Math.max(0, width / 2);
23681 					self.layoutRect({w: width, x: x});
23682 					needsRecalc = true;
23683 				}
23684 			}
23685 
23686 			// Resize window based on statusbar width
23687 			if (statusbar) {
23688 				statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
23689 
23690 				width = statusbar.layoutRect().minW + layoutRect.deltaW;
23691 				if (width > layoutRect.w) {
23692 					x = layoutRect.x - Math.max(0, width - layoutRect.w);
23693 					self.layoutRect({w: width, x: x});
23694 					needsRecalc = true;
23695 				}
23696 			}
23697 
23698 			// Recalc body and disable auto resize
23699 			if (needsRecalc) {
23700 				self.recalc();
23701 			}
23702 		},
23703 
23704 		/**
23705 		 * Initializes the current controls layout rect.
23706 		 * This will be executed by the layout managers to determine the
23707 		 * default minWidth/minHeight etc.
23708 		 *
23709 		 * @method initLayoutRect
23710 		 * @return {Object} Layout rect instance.
23711 		 */
23712 		initLayoutRect: function() {
23713 			var self = this, layoutRect = self._super(), deltaH = 0, headEl;
23714 
23715 			// Reserve vertical space for title
23716 			if (self.settings.title && !self._fullscreen) {
23717 				headEl = self.getEl('head');
23718 
23719 				var size = DomUtils.getSize(headEl);
23720 
23721 				layoutRect.headerW = size.width;
23722 				layoutRect.headerH = size.height;
23723 
23724 				deltaH += layoutRect.headerH;
23725 			}
23726 
23727 			// Reserve vertical space for statusbar
23728 			if (self.statusbar) {
23729 				deltaH += self.statusbar.layoutRect().h;
23730 			}
23731 
23732 			layoutRect.deltaH += deltaH;
23733 			layoutRect.minH += deltaH;
23734 			//layoutRect.innerH -= deltaH;
23735 			layoutRect.h += deltaH;
23736 
23737 			var rect = DomUtils.getWindowSize();
23738 
23739 			layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
23740 			layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);
23741 
23742 			return layoutRect;
23743 		},
23744 
23745 		/**
23746 		 * Renders the control as a HTML string.
23747 		 *
23748 		 * @method renderHtml
23749 		 * @return {String} HTML representing the control.
23750 		 */
23751 		renderHtml: function() {
23752 			var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
23753 			var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
23754 
23755 			self.preRender();
23756 			layout.preRender(self);
23757 
23758 			if (settings.title) {
23759 				headerHtml = (
23760 					'<div id="' + id + '-head" class="' + prefix + 'window-head">' +
23761 						'<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
23762 						'<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
23763 						'<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
23764 					'</div>'
23765 				);
23766 			}
23767 
23768 			if (settings.url) {
23769 				html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
23770 			}
23771 
23772 			if (typeof(html) == "undefined") {
23773 				html = layout.renderHtml(self);
23774 			}
23775 
23776 			if (self.statusbar) {
23777 				footerHtml = self.statusbar.renderHtml();
23778 			}
23779 
23780 			return (
23781 				'<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
23782 					'<div class="' + self.classPrefix + 'reset" role="application">' +
23783 						headerHtml +
23784 						'<div id="' + id + '-body" class="' + self.classes('body') + '">' +
23785 							html +
23786 						'</div>' +
23787 						footerHtml +
23788 					'</div>' +
23789 				'</div>'
23790 			);
23791 		},
23792 
23793 		/**
23794 		 * Switches the window fullscreen mode.
23795 		 *
23796 		 * @method fullscreen
23797 		 * @param {Boolean} state True/false state.
23798 		 * @return {tinymce.ui.Window} Current window instance.
23799 		 */
23800 		fullscreen: function(state) {
23801 			var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
23802 
23803 			if (state != self._fullscreen) {
23804 				DomUtils.on(window, 'resize', function() {
23805 					var time;
23806 
23807 					if (self._fullscreen) {
23808 						// Time the layout time if it's to slow use a timeout to not hog the CPU
23809 						if (!slowRendering) {
23810 							time = new Date().getTime();
23811 
23812 							var rect = DomUtils.getWindowSize();
23813 							self.moveTo(0, 0).resizeTo(rect.w, rect.h);
23814 
23815 							if ((new Date().getTime()) - time > 50) {
23816 								slowRendering = true;
23817 							}
23818 						} else {
23819 							if (!self._timer) {
23820 								self._timer = setTimeout(function() {
23821 									var rect = DomUtils.getWindowSize();
23822 									self.moveTo(0, 0).resizeTo(rect.w, rect.h);
23823 
23824 									self._timer = 0;
23825 								}, 50);
23826 							}
23827 						}
23828 					}
23829 				});
23830 
23831 				layoutRect = self.layoutRect();
23832 				self._fullscreen = state;
23833 
23834 				if (!state) {
23835 					self._borderBox = self.parseBox(self.settings.border);
23836 					self.getEl('head').style.display = '';
23837 					layoutRect.deltaH += layoutRect.headerH;
23838 					DomUtils.removeClass(documentElement, prefix + 'fullscreen');
23839 					DomUtils.removeClass(document.body, prefix + 'fullscreen');
23840 					self.removeClass('fullscreen');
23841 					self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
23842 				} else {
23843 					self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
23844 
23845 					self._borderBox = self.parseBox('0');
23846 					self.getEl('head').style.display = 'none';
23847 					layoutRect.deltaH -= layoutRect.headerH + 2;
23848 					DomUtils.addClass(documentElement, prefix + 'fullscreen');
23849 					DomUtils.addClass(document.body, prefix + 'fullscreen');
23850 					self.addClass('fullscreen');
23851 
23852 					var rect = DomUtils.getWindowSize();
23853 					self.moveTo(0, 0).resizeTo(rect.w, rect.h);
23854 				}
23855 			}
23856 
23857 			return self.reflow();
23858 		},
23859 
23860 		/**
23861 		 * Called after the control has been rendered.
23862 		 *
23863 		 * @method postRender
23864 		 */
23865 		postRender: function() {
23866 			var self = this, startPos;
23867 
23868 			setTimeout(function() {
23869 				self.addClass('in');
23870 			}, 0);
23871 
23872 			self._super();
23873 
23874 			if (self.statusbar) {
23875 				self.statusbar.postRender();
23876 			}
23877 
23878 			self.focus();
23879 
23880 			this.dragHelper = new DragHelper(self._id + '-dragh', {
23881 				start: function() {
23882 					startPos = {
23883 						x: self.layoutRect().x,
23884 						y: self.layoutRect().y
23885 					};
23886 				},
23887 
23888 				drag: function(e) {
23889 					self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
23890 				}
23891 			});
23892 
23893 			self.on('submit', function(e) {
23894 				if (!e.isDefaultPrevented()) {
23895 					self.close();
23896 				}
23897 			});
23898 		},
23899 
23900 		/**
23901 		 * Fires a submit event with the serialized form.
23902 		 *
23903 		 * @method submit
23904 		 * @return {Object} Event arguments object.
23905 		 */
23906 		submit: function() {
23907 			return this.fire('submit', {data: this.toJSON()});
23908 		},
23909 
23910 		/**
23911 		 * Removes the current control from DOM and from UI collections.
23912 		 *
23913 		 * @method remove
23914 		 * @return {tinymce.ui.Control} Current control instance.
23915 		 */
23916 		remove: function() {
23917 			var self = this, prefix = self.classPrefix;
23918 
23919 			self.dragHelper.destroy();
23920 			self._super();
23921 
23922 			if (self.statusbar) {
23923 				this.statusbar.remove();
23924 			}
23925 
23926 			if (self._fullscreen) {
23927 				DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
23928 				DomUtils.removeClass(document.body, prefix + 'fullscreen');
23929 			}
23930 		},
23931 
23932 		/**
23933 		 * Returns the contentWindow object of the iframe if it exists.
23934 		 *
23935 		 * @method getContentWindow
23936 		 * @return {Window} window object or null.
23937 		 */
23938 		getContentWindow: function() {
23939 			var ifr = this.getEl().getElementsByTagName('iframe')[0];
23940 			return ifr ? ifr.contentWindow : null;
23941 		}
23942 	});
23943 
23944 	return Window;
23945 });
23946 
23947 // Included from: js/tinymce/classes/ui/MessageBox.js
23948 
23949 /**
23950  * MessageBox.js
23951  *
23952  * Copyright, Moxiecode Systems AB
23953  * Released under LGPL License.
23954  *
23955  * License: http://www.tinymce.com/license
23956  * Contributing: http://www.tinymce.com/contributing
23957  */
23958 
23959 /**
23960  * This class is used to create MessageBoxes like alerts/confirms etc.
23961  *
23962  * @class tinymce.ui.MessageBox
23963  * @extends tinymce.ui.Window
23964  */
23965 define("tinymce/ui/MessageBox", [
23966 	"tinymce/ui/Window"
23967 ], function(Window) {
23968 	"use strict";
23969 
23970 	var MessageBox = Window.extend({
23971 		/**
23972 		 * Constructs a instance with the specified settings.
23973 		 *
23974 		 * @constructor
23975 		 * @param {Object} settings Name/value object with settings.
23976 		 */
23977 		init: function(settings) {
23978 			settings = {
23979 				border: 1,
23980 				padding: 20,
23981 				layout: 'flex',
23982 				pack: "center",
23983 				align: "center",
23984 				containerCls: 'panel',
23985 				autoScroll: true,
23986 				buttons: {type: "button", text: "Ok", action: "ok"},
23987 				items: {
23988 					type: "label",
23989 					multiline: true,
23990 					maxWidth: 500,
23991 					maxHeight: 200
23992 				}
23993 			};
23994 
23995 			this._super(settings);
23996 		},
23997 
23998 		Statics: {
23999 			/**
24000 			 * Ok buttons constant.
24001 			 *
24002 			 * @static
24003 			 * @final
24004 			 * @field {Number} OK
24005 			 */
24006 			OK: 1,
24007 
24008 			/**
24009 			 * Ok/cancel buttons constant.
24010 			 *
24011 			 * @static
24012 			 * @final
24013 			 * @field {Number} OK_CANCEL
24014 			 */
24015 			OK_CANCEL: 2,
24016 
24017 			/**
24018 			 * yes/no buttons constant.
24019 			 *
24020 			 * @static
24021 			 * @final
24022 			 * @field {Number} YES_NO
24023 			 */
24024 			YES_NO: 3,
24025 
24026 			/**
24027 			 * yes/no/cancel buttons constant.
24028 			 *
24029 			 * @static
24030 			 * @final
24031 			 * @field {Number} YES_NO_CANCEL
24032 			 */
24033 			YES_NO_CANCEL: 4,
24034 
24035 			/**
24036 			 * Constructs a new message box and renders it to the body element.
24037 			 *
24038 			 * @static
24039 			 * @method msgBox
24040 			 * @param {Object} settings Name/value object with settings.
24041 			 */
24042 			msgBox: function(settings) {
24043 				var buttons, callback = settings.callback || function() {};
24044 
24045 				function createButton(text, status, primary) {
24046 					return {
24047 						type: "button",
24048 						text: text,
24049 						subtype: primary ? 'primary' : '',
24050 						onClick: function(e) {
24051 							e.control.parents()[1].close();
24052 							callback(status);
24053 						}
24054 					};
24055 				}
24056 
24057 				switch (settings.buttons) {
24058 					case MessageBox.OK_CANCEL:
24059 						buttons = [
24060 							createButton('Ok', true, true),
24061 							createButton('Cancel', false)
24062 						];
24063 						break;
24064 
24065 					case MessageBox.YES_NO:
24066 					case MessageBox.YES_NO_CANCEL:
24067 						buttons = [
24068 							createButton('Yes', 1, true),
24069 							createButton('No', 0)
24070 						];
24071 
24072 						if (settings.buttons == MessageBox.YES_NO_CANCEL) {
24073 							buttons.push(createButton('Cancel', -1));
24074 						}
24075 						break;
24076 
24077 					default:
24078 						buttons = [
24079 							createButton('Ok', true, true)
24080 						];
24081 						break;
24082 				}
24083 
24084 				return new Window({
24085 					padding: 20,
24086 					x: settings.x,
24087 					y: settings.y,
24088 					minWidth: 300,
24089 					minHeight: 100,
24090 					layout: "flex",
24091 					pack: "center",
24092 					align: "center",
24093 					buttons: buttons,
24094 					title: settings.title,
24095 					role: 'alertdialog',
24096 					items: {
24097 						type: "label",
24098 						multiline: true,
24099 						maxWidth: 500,
24100 						maxHeight: 200,
24101 						text: settings.text
24102 					},
24103 					onPostRender: function() {
24104 						this.aria('describedby', this.items()[0]._id);
24105 					},
24106 					onClose: settings.onClose,
24107 					onCancel: function() {
24108 						callback(false);
24109 					}
24110 				}).renderTo(document.body).reflow();
24111 			},
24112 
24113 			/**
24114 			 * Creates a new alert dialog.
24115 			 *
24116 			 * @method alert
24117 			 * @param {Object} settings Settings for the alert dialog.
24118 			 * @param {function} [callback] Callback to execute when the user makes a choice.
24119 			 */
24120 			alert: function(settings, callback) {
24121 				if (typeof(settings) == "string") {
24122 					settings = {text: settings};
24123 				}
24124 
24125 				settings.callback = callback;
24126 				return MessageBox.msgBox(settings);
24127 			},
24128 
24129 			/**
24130 			 * Creates a new confirm dialog.
24131 			 *
24132 			 * @method confirm
24133 			 * @param {Object} settings Settings for the confirm dialog.
24134 			 * @param {function} [callback] Callback to execute when the user makes a choice.
24135 			 */
24136 			confirm: function(settings, callback) {
24137 				if (typeof(settings) == "string") {
24138 					settings = {text: settings};
24139 				}
24140 
24141 				settings.callback = callback;
24142 				settings.buttons = MessageBox.OK_CANCEL;
24143 
24144 				return MessageBox.msgBox(settings);
24145 			}
24146 		}
24147 	});
24148 
24149 	return MessageBox;
24150 });
24151 
24152 // Included from: js/tinymce/classes/WindowManager.js
24153 
24154 /**
24155  * WindowManager.js
24156  *
24157  * Copyright, Moxiecode Systems AB
24158  * Released under LGPL License.
24159  *
24160  * License: http://www.tinymce.com/license
24161  * Contributing: http://www.tinymce.com/contributing
24162  */
24163 
24164 /**
24165  * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
24166  *
24167  * @class tinymce.WindowManager
24168  * @example
24169  * // Opens a new dialog with the file.htm file and the size 320x240
24170  * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
24171  * tinymce.activeEditor.windowManager.open({
24172  *    url: 'file.htm',
24173  *    width: 320,
24174  *    height: 240
24175  * }, {
24176  *    custom_param: 1
24177  * });
24178  *
24179  * // Displays an alert box using the active editors window manager instance
24180  * tinymce.activeEditor.windowManager.alert('Hello world!');
24181  *
24182  * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
24183  * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
24184  *    if (s)
24185  *       tinymce.activeEditor.windowManager.alert("Ok");
24186  *    else
24187  *       tinymce.activeEditor.windowManager.alert("Cancel");
24188  * });
24189  */
24190 define("tinymce/WindowManager", [
24191 	"tinymce/ui/Window",
24192 	"tinymce/ui/MessageBox"
24193 ], function(Window, MessageBox) {
24194 	return function(editor) {
24195 		var self = this, windows = [];
24196 
24197 		function getTopMostWindow() {
24198 			if (windows.length) {
24199 				return windows[windows.length - 1];
24200 			}
24201 		}
24202 
24203 		self.windows = windows;
24204 
24205 		editor.on('remove', function() {
24206 			var i = windows.length;
24207 
24208 			while (i--) {
24209 				windows[i].close();
24210 			}
24211 		});
24212 
24213 		/**
24214 		 * Opens a new window.
24215 		 *
24216 		 * @method open
24217 		 * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
24218 		 * @option {String} title Window title.
24219 		 * @option {String} file URL of the file to open in the window.
24220 		 * @option {Number} width Width in pixels.
24221 		 * @option {Number} height Height in pixels.
24222 		 * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
24223 		 * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
24224 		 * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
24225 		 * larger than the popup size specified).
24226 		 */
24227 		self.open = function(args, params) {
24228 			var win;
24229 
24230 			editor.editorManager.setActive(editor);
24231 
24232 			args.title = args.title || ' ';
24233 
24234 			// Handle URL
24235 			args.url = args.url || args.file; // Legacy
24236 			if (args.url) {
24237 				args.width = parseInt(args.width || 320, 10);
24238 				args.height = parseInt(args.height || 240, 10);
24239 			}
24240 
24241 			// Handle body
24242 			if (args.body) {
24243 				args.items = {
24244 					defaults: args.defaults,
24245 					type: args.bodyType || 'form',
24246 					items: args.body
24247 				};
24248 			}
24249 
24250 			if (!args.url && !args.buttons) {
24251 				args.buttons = [
24252 					{text: 'Ok', subtype: 'primary', onclick: function() {
24253 						win.find('form')[0].submit();
24254 					}},
24255 
24256 					{text: 'Cancel', onclick: function() {
24257 						win.close();
24258 					}}
24259 				];
24260 			}
24261 
24262 			win = new Window(args);
24263 			windows.push(win);
24264 
24265 			win.on('close', function() {
24266 				var i = windows.length;
24267 
24268 				while (i--) {
24269 					if (windows[i] === win) {
24270 						windows.splice(i, 1);
24271 					}
24272 				}
24273 
24274 				if (!windows.length) {
24275 					editor.focus();
24276 				}
24277 			});
24278 
24279 			// Handle data
24280 			if (args.data) {
24281 				win.on('postRender', function() {
24282 					this.find('*').each(function(ctrl) {
24283 						var name = ctrl.name();
24284 
24285 						if (name in args.data) {
24286 							ctrl.value(args.data[name]);
24287 						}
24288 					});
24289 				});
24290 			}
24291 
24292 			// store args and parameters
24293 			win.features = args || {};
24294 			win.params = params || {};
24295 
24296 			// Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
24297 			if (windows.length === 1) {
24298 				editor.nodeChanged();
24299 			}
24300 
24301 			return win.renderTo().reflow();
24302 		};
24303 
24304 		/**
24305 		 * Creates a alert dialog. Please don't use the blocking behavior of this
24306 		 * native version use the callback method instead then it can be extended.
24307 		 *
24308 		 * @method alert
24309 		 * @param {String} message Text to display in the new alert dialog.
24310 		 * @param {function} callback Callback function to be executed after the user has selected ok.
24311 		 * @param {Object} scope Optional scope to execute the callback in.
24312 		 * @example
24313 		 * // Displays an alert box using the active editors window manager instance
24314 		 * tinymce.activeEditor.windowManager.alert('Hello world!');
24315 		 */
24316 		self.alert = function(message, callback, scope) {
24317 			MessageBox.alert(message, function() {
24318 				if (callback) {
24319 					callback.call(scope || this);
24320 				} else {
24321 					editor.focus();
24322 				}
24323 			});
24324 		};
24325 
24326 		/**
24327 		 * Creates a confirm dialog. Please don't use the blocking behavior of this
24328 		 * native version use the callback method instead then it can be extended.
24329 		 *
24330 		 * @method confirm
24331 		 * @param {String} messageText to display in the new confirm dialog.
24332 		 * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
24333 		 * @param {Object} scope Optional scope to execute the callback in.
24334 		 * @example
24335 		 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
24336 		 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
24337 		 *    if (s)
24338 		 *       tinymce.activeEditor.windowManager.alert("Ok");
24339 		 *    else
24340 		 *       tinymce.activeEditor.windowManager.alert("Cancel");
24341 		 * });
24342 		 */
24343 		self.confirm = function(message, callback, scope) {
24344 			MessageBox.confirm(message, function(state) {
24345 				callback.call(scope || this, state);
24346 			});
24347 		};
24348 
24349 		/**
24350 		 * Closes the top most window.
24351 		 *
24352 		 * @method close
24353 		 */
24354 		self.close = function() {
24355 			if (getTopMostWindow()) {
24356 				getTopMostWindow().close();
24357 			}
24358 		};
24359 
24360 		/**
24361 		 * Returns the params of the last window open call. This can be used in iframe based
24362 		 * dialog to get params passed from the tinymce plugin.
24363 		 *
24364 		 * @example
24365 		 * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
24366 		 *
24367 		 * @method getParams
24368 		 * @return {Object} Name/value object with parameters passed from windowManager.open call.
24369 		 */
24370 		self.getParams = function() {
24371 			return getTopMostWindow() ? getTopMostWindow().params : null;
24372 		};
24373 
24374 		/**
24375 		 * Sets the params of the last opened window.
24376 		 *
24377 		 * @method setParams
24378 		 * @param {Object} params Params object to set for the last opened window.
24379 		 */
24380 		self.setParams = function(params) {
24381 			if (getTopMostWindow()) {
24382 				getTopMostWindow().params = params;
24383 			}
24384 		};
24385 
24386 		/**
24387 		 * Returns the currently opened window objects.
24388 		 *
24389 		 * @method getWindows
24390 		 * @return {Array} Array of the currently opened windows.
24391 		 */
24392 		self.getWindows = function() {
24393 			return windows;
24394 		};
24395 	};
24396 });
24397 
24398 // Included from: js/tinymce/classes/util/Quirks.js
24399 
24400 /**
24401  * Quirks.js
24402  *
24403  * Copyright, Moxiecode Systems AB
24404  * Released under LGPL License.
24405  *
24406  * License: http://www.tinymce.com/license
24407  * Contributing: http://www.tinymce.com/contributing
24408  *
24409  * @ignore-file
24410  */
24411 
24412 /**
24413  * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
24414  *
24415  * @class tinymce.util.Quirks
24416  */
24417 define("tinymce/util/Quirks", [
24418 	"tinymce/util/VK",
24419 	"tinymce/dom/RangeUtils",
24420 	"tinymce/html/Node",
24421 	"tinymce/html/Entities",
24422 	"tinymce/Env",
24423 	"tinymce/util/Tools"
24424 ], function(VK, RangeUtils, Node, Entities, Env, Tools) {
24425 	return function(editor) {
24426 		var each = Tools.each;
24427 		var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
24428 			settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
24429 		var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
24430 
24431 		/**
24432 		 * Executes a command with a specific state this can be to enable/disable browser editing features.
24433 		 */
24434 		function setEditorCommandState(cmd, state) {
24435 			try {
24436 				editor.getDoc().execCommand(cmd, false, state);
24437 			} catch (ex) {
24438 				// Ignore
24439 			}
24440 		}
24441 
24442 		/**
24443 		 * Returns current IE document mode.
24444 		 */
24445 		function getDocumentMode() {
24446 			var documentMode = editor.getDoc().documentMode;
24447 
24448 			return documentMode ? documentMode : 6;
24449 		}
24450 
24451 		/**
24452 		 * Returns true/false if the event is prevented or not.
24453 		 *
24454 		 * @private
24455 		 * @param {Event} e Event object.
24456 		 * @return {Boolean} true/false if the event is prevented or not.
24457 		 */
24458 		function isDefaultPrevented(e) {
24459 			return e.isDefaultPrevented();
24460 		}
24461 
24462 		/**
24463 		 * Fixes a WebKit bug when deleting contents using backspace or delete key.
24464 		 * WebKit will produce a span element if you delete across two block elements.
24465 		 *
24466 		 * Example:
24467 		 * <h1>a</h1><p>|b</p>
24468 		 *
24469 		 * Will produce this on backspace:
24470 		 * <h1>a<span style="<all runtime styles>">b</span></p>
24471 		 *
24472 		 * This fixes the backspace to produce:
24473 		 * <h1>a|b</p>
24474 		 *
24475 		 * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
24476 		 *
24477 		 * This fixes the following delete scenarios:
24478 		 *  1. Delete by pressing backspace key.
24479 		 *  2. Delete by pressing delete key.
24480 		 *  3. Delete by pressing backspace key with ctrl/cmd (Word delete).
24481 		 *  4. Delete by pressing delete key with ctrl/cmd (Word delete).
24482 		 *  5. Delete by drag/dropping contents inside the editor.
24483 		 *  6. Delete by using Cut Ctrl+X/Cmd+X.
24484 		 *  7. Delete by selecting contents and writing a character.'
24485 		 *
24486 		 * This code is a ugly hack since writing full custom delete logic for just this bug
24487 		 * fix seemed like a huge task. I hope we can remove this before the year 2030.
24488 		 */
24489 		function cleanupStylesWhenDeleting() {
24490 			var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
24491 			var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
24492 
24493 			// Add mini polyfill for older WebKits
24494 			// TODO: Remove this when old Safari versions gets updated
24495 			if (!MutationObserver) {
24496 				olderWebKit = true;
24497 
24498 				MutationObserver = function() {
24499 					var records = [], target;
24500 
24501 					function nodeInsert(e) {
24502 						var target = e.relatedNode || e.target;
24503 						records.push({target: target, addedNodes: [target]});
24504 					}
24505 
24506 					function attrModified(e) {
24507 						var target = e.relatedNode || e.target;
24508 						records.push({target: target, attributeName: e.attrName});
24509 					}
24510 
24511 					this.observe = function(node) {
24512 						target = node;
24513 						target.addEventListener('DOMSubtreeModified', nodeInsert, false);
24514 						target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
24515 						target.addEventListener('DOMNodeInserted', nodeInsert, false);
24516 						target.addEventListener('DOMAttrModified', attrModified, false);
24517 					};
24518 
24519 					this.disconnect = function() {
24520 						target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
24521 						target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
24522 						target.removeEventListener('DOMNodeInserted', nodeInsert, false);
24523 						target.removeEventListener('DOMAttrModified', attrModified, false);
24524 					};
24525 
24526 					this.takeRecords = function() {
24527 						return records;
24528 					};
24529 				};
24530 			}
24531 
24532 			function customDelete(isForward) {
24533 				var mutationObserver = new MutationObserver(function() {});
24534 
24535 				Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
24536 					// Mark existing spans
24537 					if (elm.tagName == 'SPAN') {
24538 						elm.setAttribute('mce-data-marked', 1);
24539 					}
24540 
24541 					// Make sure all elements has a data-mce-style attribute
24542 					if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
24543 						editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style'));
24544 					}
24545 				});
24546 
24547 				// Observe added nodes and style attribute changes
24548 				mutationObserver.observe(editor.getDoc(), {
24549 					childList: true,
24550 					attributes: true,
24551 					subtree: true,
24552 					attributeFilter: ['style']
24553 				});
24554 
24555 				editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
24556 
24557 				var rng = editor.selection.getRng();
24558 				var caretElement = rng.startContainer.parentNode;
24559 
24560 				Tools.each(mutationObserver.takeRecords(), function(record) {
24561 					if (!dom.isChildOf(record.target, editor.getBody())) {
24562 						return;
24563 					}
24564 
24565 					// Restore style attribute to previous value
24566 					if (record.attributeName == "style") {
24567 						var oldValue = record.target.getAttribute('data-mce-style');
24568 
24569 						if (oldValue) {
24570 							record.target.setAttribute("style", oldValue);
24571 						} else {
24572 							record.target.removeAttribute("style");
24573 						}
24574 					}
24575 
24576 					// Remove all spans that isn't maked and retain selection
24577 					Tools.each(record.addedNodes, function(node) {
24578 						if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
24579 							var offset, container;
24580 
24581 							if (node == caretElement) {
24582 								offset = rng.startOffset;
24583 								container = node.firstChild;
24584 							}
24585 
24586 							dom.remove(node, true);
24587 
24588 							if (container) {
24589 								rng.setStart(container, offset);
24590 								rng.setEnd(container, offset);
24591 								editor.selection.setRng(rng);
24592 							}
24593 						}
24594 					});
24595 				});
24596 
24597 				mutationObserver.disconnect();
24598 
24599 				// Remove any left over marks
24600 				Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
24601 					span.removeAttribute('mce-data-marked');
24602 				});
24603 			}
24604 
24605 			editor.on('keydown', function(e) {
24606 				var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e);
24607 
24608 				if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
24609 					var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
24610 
24611 					// Ignore non meta delete in the where there is text before/after the caret
24612 					if (!isMeta && rng.collapsed && container.nodeType == 3) {
24613 						if (isForward ? offset < container.data.length : offset > 0) {
24614 							return;
24615 						}
24616 					}
24617 
24618 					e.preventDefault();
24619 
24620 					if (isMeta) {
24621 						editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word");
24622 					}
24623 
24624 					customDelete(isForward);
24625 				}
24626 			});
24627 
24628 			editor.on('keypress', function(e) {
24629 				if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
24630 					e.preventDefault();
24631 					customDelete(true);
24632 					editor.selection.setContent(String.fromCharCode(e.charCode));
24633 				}
24634 			});
24635 
24636 			editor.addCommand('Delete', function() {
24637 				customDelete();
24638 			});
24639 
24640 			editor.addCommand('ForwardDelete', function() {
24641 				customDelete(true);
24642 			});
24643 
24644 			// Older WebKits doesn't properly handle the clipboard so we can't add the rest
24645 			if (olderWebKit) {
24646 				return;
24647 			}
24648 
24649 			editor.on('dragstart', function(e) {
24650 				var selectionHtml;
24651 
24652 				if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
24653 					selection.select(e.target);
24654 				}
24655 
24656 				dragStartRng = selection.getRng();
24657 				selectionHtml = editor.selection.getContent();
24658 
24659 				// Safari doesn't support custom dataTransfer items so we can only use URL and Text
24660 				if (selectionHtml.length > 0) {
24661 					e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml));
24662 				}
24663 			});
24664 
24665 			editor.on('drop', function(e) {
24666 				if (!isDefaultPrevented(e)) {
24667 					var internalContent = e.dataTransfer.getData('URL');
24668 
24669 					if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) {
24670 						return;
24671 					}
24672 
24673 					internalContent = unescape(internalContent.substr(urlPrefix.length));
24674 					if (doc.caretRangeFromPoint) {
24675 						e.preventDefault();
24676 
24677 						// Safari has a weird issue where drag/dropping images sometimes
24678 						// produces a green plus icon. When this happens the caretRangeFromPoint
24679 						// will return "null" even though the x, y coordinate is correct.
24680 						// But if we detach the insert from the drop event we will get a proper range
24681 						window.setTimeout(function() {
24682 							var pointRng = doc.caretRangeFromPoint(e.x, e.y);
24683 
24684 							if (dragStartRng) {
24685 								selection.setRng(dragStartRng);
24686 								dragStartRng = null;
24687 							}
24688 
24689 							customDelete();
24690 
24691 							selection.setRng(pointRng);
24692 							editor.insertContent(internalContent);
24693 						}, 0);
24694 					}
24695 
24696 				}
24697 			});
24698 
24699 			editor.on('cut', function(e) {
24700 				if (!isDefaultPrevented(e) && e.clipboardData) {
24701 					e.preventDefault();
24702 					e.clipboardData.clearData();
24703 					e.clipboardData.setData('text/html', editor.selection.getContent());
24704 					e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
24705 					customDelete(true);
24706 				}
24707 			});
24708 		}
24709 
24710 		/**
24711 		 * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
24712 		 *
24713 		 * For example:
24714 		 * <p><b>|</b></p>
24715 		 *
24716 		 * Or:
24717 		 * <h1>|</h1>
24718 		 *
24719 		 * Or:
24720 		 * [<h1></h1>]
24721 		 */
24722 		function emptyEditorWhenDeleting() {
24723 			function serializeRng(rng) {
24724 				var body = dom.create("body");
24725 				var contents = rng.cloneContents();
24726 				body.appendChild(contents);
24727 				return selection.serializer.serialize(body, {format: 'html'});
24728 			}
24729 
24730 			function allContentsSelected(rng) {
24731 				if (!rng.setStart) {
24732 					if (rng.item) {
24733 						return false;
24734 					}
24735 
24736 					var bodyRng = rng.duplicate();
24737 					bodyRng.moveToElementText(editor.getBody());
24738 					return RangeUtils.compareRanges(rng, bodyRng);
24739 				}
24740 
24741 				var selection = serializeRng(rng);
24742 
24743 				var allRng = dom.createRng();
24744 				allRng.selectNode(editor.getBody());
24745 
24746 				var allSelection = serializeRng(allRng);
24747 				return selection === allSelection;
24748 			}
24749 
24750 			editor.on('keydown', function(e) {
24751 				var keyCode = e.keyCode, isCollapsed, body;
24752 
24753 				// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
24754 				if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
24755 					isCollapsed = editor.selection.isCollapsed();
24756 					body = editor.getBody();
24757 
24758 					// Selection is collapsed but the editor isn't empty
24759 					if (isCollapsed && !dom.isEmpty(body)) {
24760 						return;
24761 					}
24762 
24763 					// Selection isn't collapsed but not all the contents is selected
24764 					if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
24765 						return;
24766 					}
24767 
24768 					// Manually empty the editor
24769 					e.preventDefault();
24770 					editor.setContent('');
24771 
24772 					if (body.firstChild && dom.isBlock(body.firstChild)) {
24773 						editor.selection.setCursorLocation(body.firstChild, 0);
24774 					} else {
24775 						editor.selection.setCursorLocation(body, 0);
24776 					}
24777 
24778 					editor.nodeChanged();
24779 				}
24780 			});
24781 		}
24782 
24783 		/**
24784 		 * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
24785 		 * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
24786 		 * This selects the whole body so that backspace/delete logic will delete everything
24787 		 */
24788 		function selectAll() {
24789 			editor.shortcuts.add('ctrl+a', null, 'SelectAll');
24790 		}
24791 
24792 		/**
24793 		 * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
24794 		 * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
24795 		 *
24796 		 * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
24797 		 * you enter a character into the editor.
24798 		 *
24799 		 * It also happens when the first focus in made to the body.
24800 		 *
24801 		 * See: https://bugs.webkit.org/show_bug.cgi?id=83566
24802 		 */
24803 		function inputMethodFocus() {
24804 			if (!editor.settings.content_editable) {
24805 				// Case 1 IME doesn't initialize if you focus the document
24806 				dom.bind(editor.getDoc(), 'focusin', function() {
24807 					selection.setRng(selection.getRng());
24808 				});
24809 
24810 				// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
24811 				// Needs to be both down/up due to weird rendering bug on Chrome Windows
24812 				dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) {
24813 					if (e.target == editor.getDoc().documentElement) {
24814 						editor.getBody().focus();
24815 
24816 						if (e.type == 'mousedown') {
24817 							// Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret
24818 							selection.placeCaretAt(e.clientX, e.clientY);
24819 						} else {
24820 							selection.setRng(selection.getRng());
24821 						}
24822 					}
24823 				});
24824 			}
24825 		}
24826 
24827 		/**
24828 		 * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
24829 		 * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
24830 		 * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
24831 		 * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
24832 		 * browsers.
24833 		 *
24834 		 * It also fixes a bug on Firefox where it's impossible to delete HR elements.
24835 		 */
24836 		function removeHrOnBackspace() {
24837 			editor.on('keydown', function(e) {
24838 				if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
24839 					// Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
24840 					if (!editor.getBody().getElementsByTagName('hr').length) {
24841 						return;
24842 					}
24843 
24844 					if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
24845 						var node = selection.getNode();
24846 						var previousSibling = node.previousSibling;
24847 
24848 						if (node.nodeName == 'HR') {
24849 							dom.remove(node);
24850 							e.preventDefault();
24851 							return;
24852 						}
24853 
24854 						if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
24855 							dom.remove(previousSibling);
24856 							e.preventDefault();
24857 						}
24858 					}
24859 				}
24860 			});
24861 		}
24862 
24863 		/**
24864 		 * Firefox 3.x has an issue where the body element won't get proper focus if you click out
24865 		 * side it's rectangle.
24866 		 */
24867 		function focusBody() {
24868 			// Fix for a focus bug in FF 3.x where the body element
24869 			// wouldn't get proper focus if the user clicked on the HTML element
24870 			if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
24871 				editor.on('mousedown', function(e) {
24872 					if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
24873 						var body = editor.getBody();
24874 
24875 						// Blur the body it's focused but not correctly focused
24876 						body.blur();
24877 
24878 						// Refocus the body after a little while
24879 						setTimeout(function() {
24880 							body.focus();
24881 						}, 0);
24882 					}
24883 				});
24884 			}
24885 		}
24886 
24887 		/**
24888 		 * WebKit has a bug where it isn't possible to select image, hr or anchor elements
24889 		 * by clicking on them so we need to fake that.
24890 		 */
24891 		function selectControlElements() {
24892 			editor.on('click', function(e) {
24893 				var target = e.target;
24894 
24895 				// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
24896 				// WebKit can't even do simple things like selecting an image
24897 				// Needs to be the setBaseAndExtend or it will fail to select floated images
24898 				if (/^(IMG|HR)$/.test(target.nodeName)) {
24899 					e.preventDefault();
24900 					selection.getSel().setBaseAndExtent(target, 0, target, 1);
24901 					editor.nodeChanged();
24902 				}
24903 
24904 				if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) {
24905 					e.preventDefault();
24906 					selection.select(target);
24907 				}
24908 			});
24909 		}
24910 
24911 		/**
24912 		 * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
24913 		 *
24914 		 * Fixes do backspace/delete on this:
24915 		 * <p>bla[ck</p><p style="color:red">r]ed</p>
24916 		 *
24917 		 * Would become:
24918 		 * <p>bla|ed</p>
24919 		 *
24920 		 * Instead of:
24921 		 * <p style="color:red">bla|ed</p>
24922 		 */
24923 		function removeStylesWhenDeletingAcrossBlockElements() {
24924 			function getAttributeApplyFunction() {
24925 				var template = dom.getAttribs(selection.getStart().cloneNode(false));
24926 
24927 				return function() {
24928 					var target = selection.getStart();
24929 
24930 					if (target !== editor.getBody()) {
24931 						dom.setAttrib(target, "style", null);
24932 
24933 						each(template, function(attr) {
24934 							target.setAttributeNode(attr.cloneNode(true));
24935 						});
24936 					}
24937 				};
24938 			}
24939 
24940 			function isSelectionAcrossElements() {
24941 				return !selection.isCollapsed() &&
24942 					dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
24943 			}
24944 
24945 			editor.on('keypress', function(e) {
24946 				var applyAttributes;
24947 
24948 				if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
24949 					applyAttributes = getAttributeApplyFunction();
24950 					editor.getDoc().execCommand('delete', false, null);
24951 					applyAttributes();
24952 					e.preventDefault();
24953 					return false;
24954 				}
24955 			});
24956 
24957 			dom.bind(editor.getDoc(), 'cut', function(e) {
24958 				var applyAttributes;
24959 
24960 				if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
24961 					applyAttributes = getAttributeApplyFunction();
24962 
24963 					setTimeout(function() {
24964 						applyAttributes();
24965 					}, 0);
24966 				}
24967 			});
24968 		}
24969 
24970 		/**
24971 		 * Screen readers on IE needs to have the role application set on the body.
24972 		 */
24973 		function ensureBodyHasRoleApplication() {
24974 			document.body.setAttribute("role", "application");
24975 		}
24976 
24977 		/**
24978 		 * Backspacing into a table behaves differently depending upon browser type.
24979 		 * Therefore, disable Backspace when cursor immediately follows a table.
24980 		 */
24981 		function disableBackspaceIntoATable() {
24982 			editor.on('keydown', function(e) {
24983 				if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
24984 					if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
24985 						var previousSibling = selection.getNode().previousSibling;
24986 						if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
24987 							e.preventDefault();
24988 							return false;
24989 						}
24990 					}
24991 				}
24992 			});
24993 		}
24994 
24995 		/**
24996 		 * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
24997 		 * logic adds a \n before the BR so that it will get rendered.
24998 		 */
24999 		function addNewLinesBeforeBrInPre() {
25000 			// IE8+ rendering mode does the right thing with BR in PRE
25001 			if (getDocumentMode() > 7) {
25002 				return;
25003 			}
25004 
25005 			// Enable display: none in area and add a specific class that hides all BR elements in PRE to
25006 			// avoid the caret from getting stuck at the BR elements while pressing the right arrow key
25007 			setEditorCommandState('RespectVisibilityInDesign', true);
25008 			editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
25009 			dom.addClass(editor.getBody(), 'mceHideBrInPre');
25010 
25011 			// Adds a \n before all BR elements in PRE to get them visual
25012 			parser.addNodeFilter('pre', function(nodes) {
25013 				var i = nodes.length, brNodes, j, brElm, sibling;
25014 
25015 				while (i--) {
25016 					brNodes = nodes[i].getAll('br');
25017 					j = brNodes.length;
25018 					while (j--) {
25019 						brElm = brNodes[j];
25020 
25021 						// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
25022 						sibling = brElm.prev;
25023 						if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
25024 							sibling.value += '\n';
25025 						} else {
25026 							brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
25027 						}
25028 					}
25029 				}
25030 			});
25031 
25032 			// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
25033 			serializer.addNodeFilter('pre', function(nodes) {
25034 				var i = nodes.length, brNodes, j, brElm, sibling;
25035 
25036 				while (i--) {
25037 					brNodes = nodes[i].getAll('br');
25038 					j = brNodes.length;
25039 					while (j--) {
25040 						brElm = brNodes[j];
25041 						sibling = brElm.prev;
25042 						if (sibling && sibling.type == 3) {
25043 							sibling.value = sibling.value.replace(/\r?\n$/, '');
25044 						}
25045 					}
25046 				}
25047 			});
25048 		}
25049 
25050 		/**
25051 		 * Moves style width/height to attribute width/height when the user resizes an image on IE.
25052 		 */
25053 		function removePreSerializedStylesWhenSelectingControls() {
25054 			dom.bind(editor.getBody(), 'mouseup', function() {
25055 				var value, node = selection.getNode();
25056 
25057 				// Moved styles to attributes on IMG eements
25058 				if (node.nodeName == 'IMG') {
25059 					// Convert style width to width attribute
25060 					if ((value = dom.getStyle(node, 'width'))) {
25061 						dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
25062 						dom.setStyle(node, 'width', '');
25063 					}
25064 
25065 					// Convert style height to height attribute
25066 					if ((value = dom.getStyle(node, 'height'))) {
25067 						dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
25068 						dom.setStyle(node, 'height', '');
25069 					}
25070 				}
25071 			});
25072 		}
25073 
25074 		/**
25075 		 * Removes a blockquote when backspace is pressed at the beginning of it.
25076 		 *
25077 		 * For example:
25078 		 * <blockquote><p>|x</p></blockquote>
25079 		 *
25080 		 * Becomes:
25081 		 * <p>|x</p>
25082 		 */
25083 		function removeBlockQuoteOnBackSpace() {
25084 			// Add block quote deletion handler
25085 			editor.on('keydown', function(e) {
25086 				var rng, container, offset, root, parent;
25087 
25088 				if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
25089 					return;
25090 				}
25091 
25092 				rng = selection.getRng();
25093 				container = rng.startContainer;
25094 				offset = rng.startOffset;
25095 				root = dom.getRoot();
25096 				parent = container;
25097 
25098 				if (!rng.collapsed || offset !== 0) {
25099 					return;
25100 				}
25101 
25102 				while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
25103 					parent = parent.parentNode;
25104 				}
25105 
25106 				// Is the cursor at the beginning of a blockquote?
25107 				if (parent.tagName === 'BLOCKQUOTE') {
25108 					// Remove the blockquote
25109 					editor.formatter.toggle('blockquote', null, parent);
25110 
25111 					// Move the caret to the beginning of container
25112 					rng = dom.createRng();
25113 					rng.setStart(container, 0);
25114 					rng.setEnd(container, 0);
25115 					selection.setRng(rng);
25116 				}
25117 			});
25118 		}
25119 
25120 		/**
25121 		 * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
25122 		 */
25123 		function setGeckoEditingOptions() {
25124 			function setOpts() {
25125 				editor._refreshContentEditable();
25126 
25127 				setEditorCommandState("StyleWithCSS", false);
25128 				setEditorCommandState("enableInlineTableEditing", false);
25129 
25130 				if (!settings.object_resizing) {
25131 					setEditorCommandState("enableObjectResizing", false);
25132 				}
25133 			}
25134 
25135 			if (!settings.readonly) {
25136 				editor.on('BeforeExecCommand MouseDown', setOpts);
25137 			}
25138 		}
25139 
25140 		/**
25141 		 * Fixes a gecko link bug, when a link is placed at the end of block elements there is
25142 		 * no way to move the caret behind the link. This fix adds a bogus br element after the link.
25143 		 *
25144 		 * For example this:
25145 		 * <p><b><a href="#">x</a></b></p>
25146 		 *
25147 		 * Becomes this:
25148 		 * <p><b><a href="#">x</a></b><br></p>
25149 		 */
25150 		function addBrAfterLastLinks() {
25151 			function fixLinks() {
25152 				each(dom.select('a'), function(node) {
25153 					var parentNode = node.parentNode, root = dom.getRoot();
25154 
25155 					if (parentNode.lastChild === node) {
25156 						while (parentNode && !dom.isBlock(parentNode)) {
25157 							if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
25158 								return;
25159 							}
25160 
25161 							parentNode = parentNode.parentNode;
25162 						}
25163 
25164 						dom.add(parentNode, 'br', {'data-mce-bogus': 1});
25165 					}
25166 				});
25167 			}
25168 
25169 			editor.on('SetContent ExecCommand', function(e) {
25170 				if (e.type == "setcontent" || e.command === 'mceInsertLink') {
25171 					fixLinks();
25172 				}
25173 			});
25174 		}
25175 
25176 		/**
25177 		 * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
25178 		 * default we want to change that behavior.
25179 		 */
25180 		function setDefaultBlockType() {
25181 			if (settings.forced_root_block) {
25182 				editor.on('init', function() {
25183 					setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
25184 				});
25185 			}
25186 		}
25187 
25188 		/**
25189 		 * Removes ghost selections from images/tables on Gecko.
25190 		 */
25191 		function removeGhostSelection() {
25192 			editor.on('Undo Redo SetContent', function(e) {
25193 				if (!e.initial) {
25194 					editor.execCommand('mceRepaint');
25195 				}
25196 			});
25197 		}
25198 
25199 		/**
25200 		 * Deletes the selected image on IE instead of navigating to previous page.
25201 		 */
25202 		function deleteControlItemOnBackSpace() {
25203 			editor.on('keydown', function(e) {
25204 				var rng;
25205 
25206 				if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
25207 					rng = editor.getDoc().selection.createRange();
25208 					if (rng && rng.item) {
25209 						e.preventDefault();
25210 						editor.undoManager.beforeChange();
25211 						dom.remove(rng.item(0));
25212 						editor.undoManager.add();
25213 					}
25214 				}
25215 			});
25216 		}
25217 
25218 		/**
25219 		 * IE10 doesn't properly render block elements with the right height until you add contents to them.
25220 		 * This fixes that by adding a padding-right to all empty text block elements.
25221 		 * See: https://connect.microsoft.com/IE/feedback/details/743881
25222 		 */
25223 		function renderEmptyBlocksFix() {
25224 			var emptyBlocksCSS;
25225 
25226 			// IE10+
25227 			if (getDocumentMode() >= 10) {
25228 				emptyBlocksCSS = '';
25229 				each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
25230 					emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
25231 				});
25232 
25233 				editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
25234 			}
25235 		}
25236 
25237 		/**
25238 		 * Old IE versions can't retain contents within noscript elements so this logic will store the contents
25239 		 * as a attribute and the insert that value as it's raw text when the DOM is serialized.
25240 		 */
25241 		function keepNoScriptContents() {
25242 			if (getDocumentMode() < 9) {
25243 				parser.addNodeFilter('noscript', function(nodes) {
25244 					var i = nodes.length, node, textNode;
25245 
25246 					while (i--) {
25247 						node = nodes[i];
25248 						textNode = node.firstChild;
25249 
25250 						if (textNode) {
25251 							node.attr('data-mce-innertext', textNode.value);
25252 						}
25253 					}
25254 				});
25255 
25256 				serializer.addNodeFilter('noscript', function(nodes) {
25257 					var i = nodes.length, node, textNode, value;
25258 
25259 					while (i--) {
25260 						node = nodes[i];
25261 						textNode = nodes[i].firstChild;
25262 
25263 						if (textNode) {
25264 							textNode.value = Entities.decode(textNode.value);
25265 						} else {
25266 							// Old IE can't retain noscript value so an attribute is used to store it
25267 							value = node.attributes.map['data-mce-innertext'];
25268 							if (value) {
25269 								node.attr('data-mce-innertext', null);
25270 								textNode = new Node('#text', 3);
25271 								textNode.value = value;
25272 								textNode.raw = true;
25273 								node.append(textNode);
25274 							}
25275 						}
25276 					}
25277 				});
25278 			}
25279 		}
25280 
25281 		/**
25282 		 * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
25283 		 */
25284 		function fixCaretSelectionOfDocumentElementOnIe() {
25285 			var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
25286 
25287 			// Return range from point or null if it failed
25288 			function rngFromPoint(x, y) {
25289 				var rng = body.createTextRange();
25290 
25291 				try {
25292 					rng.moveToPoint(x, y);
25293 				} catch (ex) {
25294 					// IE sometimes throws and exception, so lets just ignore it
25295 					rng = null;
25296 				}
25297 
25298 				return rng;
25299 			}
25300 
25301 			// Fires while the selection is changing
25302 			function selectionChange(e) {
25303 				var pointRng;
25304 
25305 				// Check if the button is down or not
25306 				if (e.button) {
25307 					// Create range from mouse position
25308 					pointRng = rngFromPoint(e.x, e.y);
25309 
25310 					if (pointRng) {
25311 						// Check if pointRange is before/after selection then change the endPoint
25312 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
25313 							pointRng.setEndPoint('StartToStart', startRng);
25314 						} else {
25315 							pointRng.setEndPoint('EndToEnd', startRng);
25316 						}
25317 
25318 						pointRng.select();
25319 					}
25320 				} else {
25321 					endSelection();
25322 				}
25323 			}
25324 
25325 			// Removes listeners
25326 			function endSelection() {
25327 				var rng = doc.selection.createRange();
25328 
25329 				// If the range is collapsed then use the last start range
25330 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
25331 					startRng.select();
25332 				}
25333 
25334 				dom.unbind(doc, 'mouseup', endSelection);
25335 				dom.unbind(doc, 'mousemove', selectionChange);
25336 				startRng = started = 0;
25337 			}
25338 
25339 			// Make HTML element unselectable since we are going to handle selection by hand
25340 			doc.documentElement.unselectable = true;
25341 
25342 			// Detect when user selects outside BODY
25343 			dom.bind(doc, 'mousedown contextmenu', function(e) {
25344 				if (e.target.nodeName === 'HTML') {
25345 					if (started) {
25346 						endSelection();
25347 					}
25348 
25349 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
25350 					htmlElm = doc.documentElement;
25351 					if (htmlElm.scrollHeight > htmlElm.clientHeight) {
25352 						return;
25353 					}
25354 
25355 					started = 1;
25356 					// Setup start position
25357 					startRng = rngFromPoint(e.x, e.y);
25358 					if (startRng) {
25359 						// Listen for selection change events
25360 						dom.bind(doc, 'mouseup', endSelection);
25361 						dom.bind(doc, 'mousemove', selectionChange);
25362 
25363 						dom.getRoot().focus();
25364 						startRng.select();
25365 					}
25366 				}
25367 			});
25368 		}
25369 
25370 		/**
25371 		 * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
25372 		 * this fix will lean the caret right into the closest inline element.
25373 		 */
25374 		function normalizeSelection() {
25375 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
25376 			editor.on('keyup focusin mouseup', function(e) {
25377 				if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
25378 					selection.normalize();
25379 				}
25380 			}, true);
25381 		}
25382 
25383 		/**
25384 		 * Forces Gecko to render a broken image icon if it fails to load an image.
25385 		 */
25386 		function showBrokenImageIcon() {
25387 			editor.contentStyles.push(
25388 				'img:-moz-broken {' +
25389 					'-moz-force-broken-image-icon:1;' +
25390 					'min-width:24px;' +
25391 					'min-height:24px' +
25392 				'}'
25393 			);
25394 		}
25395 
25396 		/**
25397 		 * iOS has a bug where it's impossible to type if the document has a touchstart event
25398 		 * bound and the user touches the document while having the on screen keyboard visible.
25399 		 *
25400 		 * The touch event moves the focus to the parent document while having the caret inside the iframe
25401 		 * this fix moves the focus back into the iframe document.
25402 		 */
25403 		function restoreFocusOnKeyDown() {
25404 			if (!editor.inline) {
25405 				editor.on('keydown', function() {
25406 					if (document.activeElement == document.body) {
25407 						editor.getWin().focus();
25408 					}
25409 				});
25410 			}
25411 		}
25412 
25413 		/**
25414 		 * IE 11 has an annoying issue where you can't move focus into the editor
25415 		 * by clicking on the white area HTML element. We used to be able to to fix this with
25416 		 * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
25417 		 * object it's not possible anymore. So we need to hack in a ungly CSS to force the
25418 		 * body to be at least 150px. If the user clicks the HTML element out side this 150px region
25419 		 * we simply move the focus into the first paragraph. Not ideal since you loose the
25420 		 * positioning of the caret but goot enough for most cases.
25421 		 */
25422 		function bodyHeight() {
25423 			if (!editor.inline) {
25424 				editor.contentStyles.push('body {min-height: 150px}');
25425 				editor.on('click', function(e) {
25426 					if (e.target.nodeName == 'HTML') {
25427 						editor.getBody().focus();
25428 						editor.selection.normalize();
25429 						editor.nodeChanged();
25430 					}
25431 				});
25432 			}
25433 		}
25434 
25435 		/**
25436 		 * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
25437 		 * You might then loose all your work so we need to block that behavior and replace it with our own.
25438 		 */
25439 		function blockCmdArrowNavigation() {
25440 			if (Env.mac) {
25441 				editor.on('keydown', function(e) {
25442 					if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) {
25443 						e.preventDefault();
25444 						editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
25445 					}
25446 				});
25447 			}
25448 		}
25449 
25450 		/**
25451 		 * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
25452 		 */
25453 		function disableAutoUrlDetect() {
25454 			setEditorCommandState("AutoUrlDetect", false);
25455 		}
25456 
25457 		/**
25458 		 * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
25459 		 * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
25460 		 * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
25461 		 * but not as the lastChild of the body. However is we add a BR element to the body then remove it
25462 		 * it doesn't seem to add these BR elements makes sence right?!
25463 		 *
25464 		 * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
25465 		 */
25466 		function doubleTrailingBrElements() {
25467 			if (!editor.inline) {
25468 				editor.on('focus blur beforegetcontent', function() {
25469 					var br = editor.dom.create('br');
25470 					editor.getBody().appendChild(br);
25471 					br.parentNode.removeChild(br);
25472 				}, true);
25473 			}
25474 		}
25475 
25476 		/**
25477 		 * iOS 7.1 introduced two new bugs:
25478 		 * 1) It's possible to open links within a contentEditable area by clicking on them.
25479 		 * 2) If you hold down the finger it will display the link/image touch callout menu.
25480 		 */
25481 		function tapLinksAndImages() {
25482 			editor.on('click', function(e) {
25483 				var elm = e.target;
25484 
25485 				do {
25486 					if (elm.tagName === 'A') {
25487 						e.preventDefault();
25488 						return;
25489 					}
25490 				} while ((elm = elm.parentNode));
25491 			});
25492 
25493 			editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
25494 		}
25495 
25496 		/**
25497 		 * iOS Safari and possible other browsers have a bug where it won't fire
25498 		 * a click event when a contentEditable is focused. This function fakes click events
25499 		 * by using touchstart/touchend and measuring the time and distance travelled.
25500 		 */
25501 		function touchClickEvent() {
25502 			editor.on('touchstart', function(e) {
25503 				var elm, time, startTouch, changedTouches;
25504 
25505 				elm = e.target;
25506 				time = new Date().getTime();
25507 				changedTouches = e.changedTouches;
25508 
25509 				if (!changedTouches || changedTouches.length > 1) {
25510 					return;
25511 				}
25512 
25513 				startTouch = changedTouches[0];
25514 
25515 				editor.once('touchend', function(e) {
25516 					var endTouch = e.changedTouches[0], args;
25517 
25518 					if (new Date().getTime() - time > 500) {
25519 						return;
25520 					}
25521 
25522 					if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) {
25523 						return;
25524 					}
25525 
25526 					if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) {
25527 						return;
25528 					}
25529 
25530 					args = {
25531 						target: elm
25532 					};
25533 
25534 					each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) {
25535 						args[key] = endTouch[key];
25536 					});
25537 
25538 					args = editor.fire('click', args);
25539 
25540 					if (!args.isDefaultPrevented()) {
25541 						// iOS WebKit can't place the caret properly once
25542 						// you bind touch events so we need to do this manually
25543 						// TODO: Expand to the closest word? Touble tap still works.
25544 						editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
25545 						editor.nodeChanged();
25546 					}
25547 				});
25548 			});
25549 		}
25550 
25551 		/**
25552 		 * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
25553 		 * For example this: <form><button></form>
25554 		 */
25555 		function blockFormSubmitInsideEditor() {
25556 			editor.on('init', function() {
25557 				editor.dom.bind(editor.getBody(), 'submit', function(e) {
25558 					e.preventDefault();
25559 				});
25560 			});
25561 		}
25562 
25563 		/**
25564 		 * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
25565 		 *
25566 		 * Scenario:
25567 		 *  1) Create a table 2x2.
25568 		 *  2) Select and copy cells A2-B2.
25569 		 *  3) Paste and it will add BR element to table cell.
25570 		 */
25571 		function removeAppleInterchangeBrs() {
25572 			parser.addNodeFilter('br', function(nodes) {
25573 				var i = nodes.length;
25574 
25575 				while (i--) {
25576 					if (nodes[i].attr('class') == 'Apple-interchange-newline') {
25577 						nodes[i].remove();
25578 					}
25579 				}
25580 			});
25581 		}
25582 
25583 		// All browsers
25584 		removeBlockQuoteOnBackSpace();
25585 		emptyEditorWhenDeleting();
25586 		normalizeSelection();
25587 
25588 		// WebKit
25589 		if (isWebKit) {
25590 			cleanupStylesWhenDeleting();
25591 			inputMethodFocus();
25592 			selectControlElements();
25593 			setDefaultBlockType();
25594 			blockFormSubmitInsideEditor();
25595 			disableBackspaceIntoATable();
25596 			removeAppleInterchangeBrs();
25597 			touchClickEvent();
25598 
25599 			// iOS
25600 			if (Env.iOS) {
25601 				restoreFocusOnKeyDown();
25602 				bodyHeight();
25603 				tapLinksAndImages();
25604 			} else {
25605 				selectAll();
25606 			}
25607 		}
25608 
25609 		// IE
25610 		if (isIE && Env.ie < 11) {
25611 			removeHrOnBackspace();
25612 			ensureBodyHasRoleApplication();
25613 			addNewLinesBeforeBrInPre();
25614 			removePreSerializedStylesWhenSelectingControls();
25615 			deleteControlItemOnBackSpace();
25616 			renderEmptyBlocksFix();
25617 			keepNoScriptContents();
25618 			fixCaretSelectionOfDocumentElementOnIe();
25619 		}
25620 
25621 		if (Env.ie >= 11) {
25622 			bodyHeight();
25623 			doubleTrailingBrElements();
25624 			disableBackspaceIntoATable();
25625 		}
25626 
25627 		if (Env.ie) {
25628 			selectAll();
25629 			disableAutoUrlDetect();
25630 		}
25631 
25632 		// Gecko
25633 		if (isGecko) {
25634 			removeHrOnBackspace();
25635 			focusBody();
25636 			removeStylesWhenDeletingAcrossBlockElements();
25637 			setGeckoEditingOptions();
25638 			addBrAfterLastLinks();
25639 			removeGhostSelection();
25640 			showBrokenImageIcon();
25641 			blockCmdArrowNavigation();
25642 			disableBackspaceIntoATable();
25643 		}
25644 	};
25645 });
25646 
25647 // Included from: js/tinymce/classes/util/Observable.js
25648 
25649 /**
25650  * Observable.js
25651  *
25652  * Copyright, Moxiecode Systems AB
25653  * Released under LGPL License.
25654  *
25655  * License: http://www.tinymce.com/license
25656  * Contributing: http://www.tinymce.com/contributing
25657  */
25658 
25659 /**
25660  * This mixin will add event binding logic to classes.
25661  *
25662  * @mixin tinymce.util.Observable
25663  */
25664 define("tinymce/util/Observable", [
25665 	"tinymce/util/EventDispatcher"
25666 ], function(EventDispatcher) {
25667 	function getEventDispatcher(obj) {
25668 		if (!obj._eventDispatcher) {
25669 			obj._eventDispatcher = new EventDispatcher({
25670 				scope: obj,
25671 				toggleEvent: function(name, state) {
25672 					if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
25673 						obj.toggleNativeEvent(name, state);
25674 					}
25675 				}
25676 			});
25677 		}
25678 
25679 		return obj._eventDispatcher;
25680 	}
25681 
25682 	return {
25683 		/**
25684 		 * Fires the specified event by name.
25685 		 *
25686 		 * @method fire
25687 		 * @param {String} name Name of the event to fire.
25688 		 * @param {Object?} args Event arguments.
25689 		 * @param {Boolean?} bubble True/false if the event is to be bubbled.
25690 		 * @return {Object} Event args instance passed in.
25691 		 * @example
25692 		 * instance.fire('event', {...});
25693 		 */
25694 		fire: function(name, args, bubble) {
25695 			var self = this;
25696 
25697 			// Prevent all events except the remove event after the instance has been removed
25698 			if (self.removed && name !== "remove") {
25699 				return args;
25700 			}
25701 
25702 			args = getEventDispatcher(self).fire(name, args, bubble);
25703 
25704 			// Bubble event up to parents
25705 			if (bubble !== false && self.parent) {
25706 				var parent = self.parent();
25707 				while (parent && !args.isPropagationStopped()) {
25708 					parent.fire(name, args, false);
25709 					parent = parent.parent();
25710 				}
25711 			}
25712 
25713 			return args;
25714 		},
25715 
25716 		/**
25717 		 * Binds an event listener to a specific event by name.
25718 		 *
25719 		 * @method on
25720 		 * @param {String} name Event name or space separated list of events to bind.
25721 		 * @param {callback} callback Callback to be executed when the event occurs.
25722 		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
25723 		 * @return {Object} Current class instance.
25724 		 * @example
25725 		 * instance.on('event', function(e) {
25726 		 *     // Callback logic
25727 		 * });
25728 		 */
25729 		on: function(name, callback, prepend) {
25730 			return getEventDispatcher(this).on(name, callback, prepend);
25731 		},
25732 
25733 		/**
25734 		 * Unbinds an event listener to a specific event by name.
25735 		 *
25736 		 * @method off
25737 		 * @param {String?} name Name of the event to unbind.
25738 		 * @param {callback?} callback Callback to unbind.
25739 		 * @return {Object} Current class instance.
25740 		 * @example
25741 		 * // Unbind specific callback
25742 		 * instance.off('event', handler);
25743 		 *
25744 		 * // Unbind all listeners by name
25745 		 * instance.off('event');
25746 		 *
25747 		 * // Unbind all events
25748 		 * instance.off();
25749 		 */
25750 		off: function(name, callback) {
25751 			return getEventDispatcher(this).off(name, callback);
25752 		},
25753 
25754 		/**
25755 		 * Bind the event callback and once it fires the callback is removed.
25756 		 *
25757 		 * @method once
25758 		 * @param {String} name Name of the event to bind.
25759 		 * @param {callback} callback Callback to bind only once.
25760 		 * @return {Object} Current class instance.
25761 		 */
25762 		once: function(name, callback) {
25763 			return getEventDispatcher(this).once(name, callback);
25764 		},
25765 
25766 		/**
25767 		 * Returns true/false if the object has a event of the specified name.
25768 		 *
25769 		 * @method hasEventListeners
25770 		 * @param {String} name Name of the event to check for.
25771 		 * @return {Boolean} true/false if the event exists or not.
25772 		 */
25773 		hasEventListeners: function(name) {
25774 			return getEventDispatcher(this).has(name);
25775 		}
25776 	};
25777 });
25778 
25779 // Included from: js/tinymce/classes/EditorObservable.js
25780 
25781 /**
25782  * EditorObservable.js
25783  *
25784  * Copyright, Moxiecode Systems AB
25785  * Released under LGPL License.
25786  *
25787  * License: http://www.tinymce.com/license
25788  * Contributing: http://www.tinymce.com/contributing
25789  */
25790 
25791 /**
25792  * This mixin contains the event logic for the tinymce.Editor class.
25793  *
25794  * @mixin tinymce.EditorObservable
25795  * @extends tinymce.util.Observable
25796  */
25797 define("tinymce/EditorObservable", [
25798 	"tinymce/util/Observable",
25799 	"tinymce/dom/DOMUtils",
25800 	"tinymce/util/Tools"
25801 ], function(Observable, DOMUtils, Tools) {
25802 	var DOM = DOMUtils.DOM, customEventRootDelegates;
25803 
25804 	/**
25805 	 * Returns the event target so for the specified event. Some events fire
25806 	 * only on document, some fire on documentElement etc. This also handles the
25807 	 * custom event root setting where it returns that element instead of the body.
25808 	 *
25809 	 * @private
25810 	 * @param {tinymce.Editor} editor Editor instance to get event target from.
25811 	 * @param {String} eventName Name of the event for example "click".
25812 	 * @return {Element/Document} HTML Element or document target to bind on.
25813 	 */
25814 	function getEventTarget(editor, eventName) {
25815 		if (eventName == 'selectionchange') {
25816 			return editor.getDoc();
25817 		}
25818 
25819 		// Need to bind mousedown/mouseup etc to document not body in iframe mode
25820 		// Since the user might click on the HTML element not the BODY
25821 		if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
25822 			return editor.getDoc().documentElement;
25823 		}
25824 
25825 		// Bind to event root instead of body if it's defined
25826 		if (editor.settings.event_root) {
25827 			if (!editor.eventRoot) {
25828 				editor.eventRoot = DOM.select(editor.settings.event_root)[0];
25829 			}
25830 
25831 			return editor.eventRoot;
25832 		}
25833 
25834 		return editor.getBody();
25835 	}
25836 
25837 	/**
25838 	 * Binds a event delegate for the specified name this delegate will fire
25839 	 * the event to the editor dispatcher.
25840 	 *
25841 	 * @private
25842 	 * @param {tinymce.Editor} editor Editor instance to get event target from.
25843 	 * @param {String} eventName Name of the event for example "click".
25844 	 */
25845 	function bindEventDelegate(editor, eventName) {
25846 		var eventRootElm = getEventTarget(editor, eventName), delegate;
25847 
25848 		if (!editor.delegates) {
25849 			editor.delegates = {};
25850 		}
25851 
25852 		if (editor.delegates[eventName]) {
25853 			return;
25854 		}
25855 
25856 		if (editor.settings.event_root) {
25857 			if (!customEventRootDelegates) {
25858 				customEventRootDelegates = {};
25859 				editor.editorManager.on('removeEditor', function() {
25860 					var name;
25861 
25862 					if (!editor.editorManager.activeEditor) {
25863 						if (customEventRootDelegates) {
25864 							for (name in customEventRootDelegates) {
25865 								editor.dom.unbind(getEventTarget(editor, name));
25866 							}
25867 
25868 							customEventRootDelegates = null;
25869 						}
25870 					}
25871 				});
25872 			}
25873 
25874 			if (customEventRootDelegates[eventName]) {
25875 				return;
25876 			}
25877 
25878 			delegate = function(e) {
25879 				var target = e.target, editors = editor.editorManager.editors, i = editors.length;
25880 
25881 				while (i--) {
25882 					var body = editors[i].getBody();
25883 
25884 					if (body === target || DOM.isChildOf(target, body)) {
25885 						if (!editors[i].hidden) {
25886 							editors[i].fire(eventName, e);
25887 						}
25888 					}
25889 				}
25890 			};
25891 
25892 			customEventRootDelegates[eventName] = delegate;
25893 			DOM.bind(eventRootElm, eventName, delegate);
25894 		} else {
25895 			delegate = function(e) {
25896 				if (!editor.hidden) {
25897 					editor.fire(eventName, e);
25898 				}
25899 			};
25900 
25901 			DOM.bind(eventRootElm, eventName, delegate);
25902 			editor.delegates[eventName] = delegate;
25903 		}
25904 	}
25905 
25906 	var EditorObservable = {
25907 		/**
25908 		 * Bind any pending event delegates. This gets executed after the target body/document is created.
25909 		 *
25910 		 * @private
25911 		 */
25912 		bindPendingEventDelegates: function() {
25913 			var self = this;
25914 
25915 			Tools.each(self._pendingNativeEvents, function(name) {
25916 				bindEventDelegate(self, name);
25917 			});
25918 		},
25919 
25920 		/**
25921 		 * Toggles a native event on/off this is called by the EventDispatcher when
25922 		 * the first native event handler is added and when the last native event handler is removed.
25923 		 *
25924 		 * @private
25925 		 */
25926 		toggleNativeEvent: function(name, state) {
25927 			var self = this;
25928 
25929 			if (self.settings.readonly) {
25930 				return;
25931 			}
25932 
25933 			// Never bind focus/blur since the FocusManager fakes those
25934 			if (name == "focus" || name == "blur") {
25935 				return;
25936 			}
25937 
25938 			if (state) {
25939 				if (self.initialized) {
25940 					bindEventDelegate(self, name);
25941 				} else {
25942 					if (!self._pendingNativeEvents) {
25943 						self._pendingNativeEvents = [name];
25944 					} else {
25945 						self._pendingNativeEvents.push(name);
25946 					}
25947 				}
25948 			} else if (self.initialized) {
25949 				self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
25950 				delete self.delegates[name];
25951 			}
25952 		},
25953 
25954 		/**
25955 		 * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
25956 		 *
25957 		 * @private
25958 		 */
25959 		unbindAllNativeEvents: function() {
25960 			var self = this, name;
25961 
25962 			if (self.delegates) {
25963 				for (name in self.delegates) {
25964 					self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
25965 				}
25966 
25967 				delete self.delegates;
25968 			}
25969 
25970 			if (!self.inline) {
25971 				self.getBody().onload = null;
25972 				self.dom.unbind(self.getWin());
25973 				self.dom.unbind(self.getDoc());
25974 			}
25975 
25976 			self.dom.unbind(self.getBody());
25977 			self.dom.unbind(self.getContainer());
25978 		}
25979 	};
25980 
25981 	EditorObservable = Tools.extend({}, Observable, EditorObservable);
25982 
25983 	return EditorObservable;
25984 });
25985 
25986 // Included from: js/tinymce/classes/Shortcuts.js
25987 
25988 /**
25989  * Shortcuts.js
25990  *
25991  * Copyright, Moxiecode Systems AB
25992  * Released under LGPL License.
25993  *
25994  * License: http://www.tinymce.com/license
25995  * Contributing: http://www.tinymce.com/contributing
25996  */
25997 
25998 /**
25999  * Contains all logic for handling of keyboard shortcuts.
26000  */
26001 define("tinymce/Shortcuts", [
26002 	"tinymce/util/Tools",
26003 	"tinymce/Env"
26004 ], function(Tools, Env) {
26005 	var each = Tools.each, explode = Tools.explode;
26006 
26007 	var keyCodeLookup = {
26008 		"f9": 120,
26009 		"f10": 121,
26010 		"f11": 122
26011 	};
26012 
26013 	return function(editor) {
26014 		var self = this, shortcuts = {};
26015 
26016 		editor.on('keyup keypress keydown', function(e) {
26017 			if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) {
26018 				each(shortcuts, function(shortcut) {
26019 					var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey;
26020 
26021 					if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
26022 						return;
26023 					}
26024 
26025 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
26026 						e.preventDefault();
26027 
26028 						if (e.type == "keydown") {
26029 							shortcut.func.call(shortcut.scope);
26030 						}
26031 
26032 						return true;
26033 					}
26034 				});
26035 			}
26036 		});
26037 
26038 		/**
26039 		 * Adds a keyboard shortcut for some command or function.
26040 		 *
26041 		 * @method addShortcut
26042 		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
26043 		 * @param {String} desc Text description for the command.
26044 		 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
26045 		 * @param {Object} sc Optional scope to execute the function in.
26046 		 * @return {Boolean} true/false state if the shortcut was added or not.
26047 		 */
26048 		self.add = function(pattern, desc, cmdFunc, scope) {
26049 			var cmd;
26050 
26051 			cmd = cmdFunc;
26052 
26053 			if (typeof(cmdFunc) === 'string') {
26054 				cmdFunc = function() {
26055 					editor.execCommand(cmd, false, null);
26056 				};
26057 			} else if (Tools.isArray(cmd)) {
26058 				cmdFunc = function() {
26059 					editor.execCommand(cmd[0], cmd[1], cmd[2]);
26060 				};
26061 			}
26062 
26063 			each(explode(pattern.toLowerCase()), function(pattern) {
26064 				var shortcut = {
26065 					func: cmdFunc,
26066 					scope: scope || editor,
26067 					desc: editor.translate(desc),
26068 					alt: false,
26069 					ctrl: false,
26070 					shift: false
26071 				};
26072 
26073 				each(explode(pattern, '+'), function(value) {
26074 					switch (value) {
26075 						case 'alt':
26076 						case 'ctrl':
26077 						case 'shift':
26078 							shortcut[value] = true;
26079 							break;
26080 
26081 						default:
26082 							// Allow numeric keycodes like ctrl+219 for ctrl+[
26083 							if (/^[0-9]{2,}$/.test(value)) {
26084 								shortcut.keyCode = parseInt(value, 10);
26085 							} else {
26086 								shortcut.charCode = value.charCodeAt(0);
26087 								shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
26088 							}
26089 					}
26090 				});
26091 
26092 				shortcuts[
26093 					(shortcut.ctrl ? 'ctrl' : '') + ',' +
26094 					(shortcut.alt ? 'alt' : '') + ',' +
26095 					(shortcut.shift ? 'shift' : '') + ',' +
26096 					shortcut.keyCode
26097 				] = shortcut;
26098 			});
26099 
26100 			return true;
26101 		};
26102 	};
26103 });
26104 
26105 // Included from: js/tinymce/classes/Editor.js
26106 
26107 /**
26108  * Editor.js
26109  *
26110  * Copyright, Moxiecode Systems AB
26111  * Released under LGPL License.
26112  *
26113  * License: http://www.tinymce.com/license
26114  * Contributing: http://www.tinymce.com/contributing
26115  */
26116 
26117 /*jshint scripturl:true */
26118 
26119 /**
26120  * Include the base event class documentation.
26121  *
26122  * @include ../../../tools/docs/tinymce.Event.js
26123  */
26124 
26125 /**
26126  * This class contains the core logic for a TinyMCE editor.
26127  *
26128  * @class tinymce.Editor
26129  * @mixes tinymce.util.Observable
26130  * @example
26131  * // Add a class to all paragraphs in the editor.
26132  * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
26133  *
26134  * // Gets the current editors selection as text
26135  * tinymce.activeEditor.selection.getContent({format: 'text'});
26136  *
26137  * // Creates a new editor instance
26138  * var ed = new tinymce.Editor('textareaid', {
26139  *     some_setting: 1
26140  * }, tinymce.EditorManager);
26141  *
26142  * // Select each item the user clicks on
26143  * ed.on('click', function(e) {
26144  *     ed.selection.select(e.target);
26145  * });
26146  *
26147  * ed.render();
26148  */
26149 define("tinymce/Editor", [
26150 	"tinymce/dom/DOMUtils",
26151 	"tinymce/dom/DomQuery",
26152 	"tinymce/AddOnManager",
26153 	"tinymce/NodeChange",
26154 	"tinymce/html/Node",
26155 	"tinymce/dom/Serializer",
26156 	"tinymce/html/Serializer",
26157 	"tinymce/dom/Selection",
26158 	"tinymce/Formatter",
26159 	"tinymce/UndoManager",
26160 	"tinymce/EnterKey",
26161 	"tinymce/ForceBlocks",
26162 	"tinymce/EditorCommands",
26163 	"tinymce/util/URI",
26164 	"tinymce/dom/ScriptLoader",
26165 	"tinymce/dom/EventUtils",
26166 	"tinymce/WindowManager",
26167 	"tinymce/html/Schema",
26168 	"tinymce/html/DomParser",
26169 	"tinymce/util/Quirks",
26170 	"tinymce/Env",
26171 	"tinymce/util/Tools",
26172 	"tinymce/EditorObservable",
26173 	"tinymce/Shortcuts"
26174 ], function(
26175 	DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
26176 	Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
26177 	URI, ScriptLoader, EventUtils, WindowManager,
26178 	Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts
26179 ) {
26180 	// Shorten these names
26181 	var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
26182 	var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
26183 	var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
26184 	var Event = EventUtils.Event;
26185 	var isGecko = Env.gecko, ie = Env.ie;
26186 
26187 	/**
26188 	 * Include documentation for all the events.
26189 	 *
26190 	 * @include ../../../tools/docs/tinymce.Editor.js
26191 	 */
26192 
26193 	/**
26194 	 * Constructs a editor instance by id.
26195 	 *
26196 	 * @constructor
26197 	 * @method Editor
26198 	 * @param {String} id Unique id for the editor.
26199 	 * @param {Object} settings Settings for the editor.
26200 	 * @param {tinymce.EditorManager} editorManager EditorManager instance.
26201 	 * @author Moxiecode
26202 	 */
26203 	function Editor(id, settings, editorManager) {
26204 		var self = this, documentBaseUrl, baseUri;
26205 
26206 		documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
26207 		baseUri = editorManager.baseURI;
26208 
26209 		/**
26210 		 * Name/value collection with editor settings.
26211 		 *
26212 		 * @property settings
26213 		 * @type Object
26214 		 * @example
26215 		 * // Get the value of the theme setting
26216 		 * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
26217 		 */
26218 		self.settings = settings = extend({
26219 			id: id,
26220 			theme: 'modern',
26221 			delta_width: 0,
26222 			delta_height: 0,
26223 			popup_css: '',
26224 			plugins: '',
26225 			document_base_url: documentBaseUrl,
26226 			add_form_submit_trigger: true,
26227 			submit_patch: true,
26228 			add_unload_trigger: true,
26229 			convert_urls: true,
26230 			relative_urls: true,
26231 			remove_script_host: true,
26232 			object_resizing: true,
26233 			doctype: '<!DOCTYPE html>',
26234 			visual: true,
26235 			font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
26236 
26237 			// See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
26238 			font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
26239 			forced_root_block: 'p',
26240 			hidden_input: true,
26241 			padd_empty_editor: true,
26242 			render_ui: true,
26243 			indentation: '30px',
26244 			inline_styles: true,
26245 			convert_fonts_to_spans: true,
26246 			indent: 'simple',
26247 			indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26248 				'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
26249 			indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26250 				'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
26251 			validate: true,
26252 			entity_encoding: 'named',
26253 			url_converter: self.convertURL,
26254 			url_converter_scope: self,
26255 			ie7_compat: true
26256 		}, settings);
26257 
26258 		AddOnManager.language = settings.language || 'en';
26259 		AddOnManager.languageLoad = settings.language_load;
26260 
26261 		AddOnManager.baseURL = editorManager.baseURL;
26262 
26263 		/**
26264 		 * Editor instance id, normally the same as the div/textarea that was replaced.
26265 		 *
26266 		 * @property id
26267 		 * @type String
26268 		 */
26269 		self.id = settings.id = id;
26270 
26271 		/**
26272 		 * State to force the editor to return false on a isDirty call.
26273 		 *
26274 		 * @property isNotDirty
26275 		 * @type Boolean
26276 		 * @example
26277 		 * function ajaxSave() {
26278 		 *     var ed = tinymce.get('elm1');
26279 		 *
26280 		 *     // Save contents using some XHR call
26281 		 *     alert(ed.getContent());
26282 		 *
26283 		 *     ed.isNotDirty = true; // Force not dirty state
26284 		 * }
26285 		 */
26286 		self.isNotDirty = true;
26287 
26288 		/**
26289 		 * Name/Value object containting plugin instances.
26290 		 *
26291 		 * @property plugins
26292 		 * @type Object
26293 		 * @example
26294 		 * // Execute a method inside a plugin directly
26295 		 * tinymce.activeEditor.plugins.someplugin.someMethod();
26296 		 */
26297 		self.plugins = {};
26298 
26299 		/**
26300 		 * URI object to document configured for the TinyMCE instance.
26301 		 *
26302 		 * @property documentBaseURI
26303 		 * @type tinymce.util.URI
26304 		 * @example
26305 		 * // Get relative URL from the location of document_base_url
26306 		 * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
26307 		 *
26308 		 * // Get absolute URL from the location of document_base_url
26309 		 * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
26310 		 */
26311 		self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
26312 			base_uri: baseUri
26313 		});
26314 
26315 		/**
26316 		 * URI object to current document that holds the TinyMCE editor instance.
26317 		 *
26318 		 * @property baseURI
26319 		 * @type tinymce.util.URI
26320 		 * @example
26321 		 * // Get relative URL from the location of the API
26322 		 * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
26323 		 *
26324 		 * // Get absolute URL from the location of the API
26325 		 * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
26326 		 */
26327 		self.baseURI = baseUri;
26328 
26329 		/**
26330 		 * Array with CSS files to load into the iframe.
26331 		 *
26332 		 * @property contentCSS
26333 		 * @type Array
26334 		 */
26335 		self.contentCSS = [];
26336 
26337 		/**
26338 		 * Array of CSS styles to add to head of document when the editor loads.
26339 		 *
26340 		 * @property contentStyles
26341 		 * @type Array
26342 		 */
26343 		self.contentStyles = [];
26344 
26345 		// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
26346 		self.shortcuts = new Shortcuts(self);
26347 
26348 		// Internal command handler objects
26349 		self.execCommands = {};
26350 		self.queryStateCommands = {};
26351 		self.queryValueCommands = {};
26352 		self.loadedCSS = {};
26353 
26354 		if (settings.target) {
26355 			self.targetElm = settings.target;
26356 		}
26357 
26358 		self.suffix = editorManager.suffix;
26359 		self.editorManager = editorManager;
26360 		self.inline = settings.inline;
26361 
26362 		// Call setup
26363 		editorManager.fire('SetupEditor', self);
26364 		self.execCallback('setup', self);
26365 
26366 		/**
26367 		 * Dom query instance with default scope to the editor document and default element is the body of the editor.
26368 		 *
26369 		 * @property $
26370 		 * @type tinymce.dom.DomQuery
26371 		 * @example
26372 		 * tinymce.activeEditor.$('p').css('color', 'red');
26373 		 * tinymce.activeEditor.$().append('<p>new</p>');
26374 		 */
26375 		self.$ = DomQuery.overrideDefaults(function() {
26376 			return {
26377 				context: self.inline ? self.getBody() : self.getDoc(),
26378 				element: self.getBody()
26379 			};
26380 		});
26381 	}
26382 
26383 	Editor.prototype = {
26384 		/**
26385 		 * Renderes the editor/adds it to the page.
26386 		 *
26387 		 * @method render
26388 		 */
26389 		render: function() {
26390 			var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
26391 
26392 			function readyHandler() {
26393 				DOM.unbind(window, 'ready', readyHandler);
26394 				self.render();
26395 			}
26396 
26397 			// Page is not loaded yet, wait for it
26398 			if (!Event.domLoaded) {
26399 				DOM.bind(window, 'ready', readyHandler);
26400 				return;
26401 			}
26402 
26403 			// Element not found, then skip initialization
26404 			if (!self.getElement()) {
26405 				return;
26406 			}
26407 
26408 			// No editable support old iOS versions etc
26409 			if (!Env.contentEditable) {
26410 				return;
26411 			}
26412 
26413 			// Hide target element early to prevent content flashing
26414 			if (!settings.inline) {
26415 				self.orgVisibility = self.getElement().style.visibility;
26416 				self.getElement().style.visibility = 'hidden';
26417 			} else {
26418 				self.inline = true;
26419 			}
26420 
26421 			var form = self.getElement().form || DOM.getParent(id, 'form');
26422 			if (form) {
26423 				self.formElement = form;
26424 
26425 				// Add hidden input for non input elements inside form elements
26426 				if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
26427 					DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
26428 					self.hasHiddenInput = true;
26429 				}
26430 
26431 				// Pass submit/reset from form to editor instance
26432 				self.formEventDelegate = function(e) {
26433 					self.fire(e.type, e);
26434 				};
26435 
26436 				DOM.bind(form, 'submit reset', self.formEventDelegate);
26437 
26438 				// Reset contents in editor when the form is reset
26439 				self.on('reset', function() {
26440 					self.setContent(self.startContent, {format: 'raw'});
26441 				});
26442 
26443 				// Check page uses id="submit" or name="submit" for it's submit button
26444 				if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
26445 					form._mceOldSubmit = form.submit;
26446 					form.submit = function() {
26447 						self.editorManager.triggerSave();
26448 						self.isNotDirty = true;
26449 
26450 						return form._mceOldSubmit(form);
26451 					};
26452 				}
26453 			}
26454 
26455 			/**
26456 			 * Window manager reference, use this to open new windows and dialogs.
26457 			 *
26458 			 * @property windowManager
26459 			 * @type tinymce.WindowManager
26460 			 * @example
26461 			 * // Shows an alert message
26462 			 * tinymce.activeEditor.windowManager.alert('Hello world!');
26463 			 *
26464 			 * // Opens a new dialog with the file.htm file and the size 320x240
26465 			 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
26466 			 * tinymce.activeEditor.windowManager.open({
26467 			 *    url: 'file.htm',
26468 			 *    width: 320,
26469 			 *    height: 240
26470 			 * }, {
26471 			 *    custom_param: 1
26472 			 * });
26473 			 */
26474 			self.windowManager = new WindowManager(self);
26475 
26476 			if (settings.encoding == 'xml') {
26477 				self.on('GetContent', function(e) {
26478 					if (e.save) {
26479 						e.content = DOM.encode(e.content);
26480 					}
26481 				});
26482 			}
26483 
26484 			if (settings.add_form_submit_trigger) {
26485 				self.on('submit', function() {
26486 					if (self.initialized) {
26487 						self.save();
26488 					}
26489 				});
26490 			}
26491 
26492 			if (settings.add_unload_trigger) {
26493 				self._beforeUnload = function() {
26494 					if (self.initialized && !self.destroyed && !self.isHidden()) {
26495 						self.save({format: 'raw', no_events: true, set_dirty: false});
26496 					}
26497 				};
26498 
26499 				self.editorManager.on('BeforeUnload', self._beforeUnload);
26500 			}
26501 
26502 			// Load scripts
26503 			function loadScripts() {
26504 				var scriptLoader = ScriptLoader.ScriptLoader;
26505 
26506 				if (settings.language && settings.language != 'en' && !settings.language_url) {
26507 					settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
26508 				}
26509 
26510 				if (settings.language_url) {
26511 					scriptLoader.add(settings.language_url);
26512 				}
26513 
26514 				if (settings.theme && typeof settings.theme != "function" &&
26515 					settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
26516 					var themeUrl = settings.theme_url;
26517 
26518 					if (themeUrl) {
26519 						themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
26520 					} else {
26521 						themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
26522 					}
26523 
26524 					ThemeManager.load(settings.theme, themeUrl);
26525 				}
26526 
26527 				if (Tools.isArray(settings.plugins)) {
26528 					settings.plugins = settings.plugins.join(' ');
26529 				}
26530 
26531 				each(settings.external_plugins, function(url, name) {
26532 					PluginManager.load(name, url);
26533 					settings.plugins += ' ' + name;
26534 				});
26535 
26536 				each(settings.plugins.split(/[ ,]/), function(plugin) {
26537 					plugin = trim(plugin);
26538 
26539 					if (plugin && !PluginManager.urls[plugin]) {
26540 						if (plugin.charAt(0) == '-') {
26541 							plugin = plugin.substr(1, plugin.length);
26542 
26543 							var dependencies = PluginManager.dependencies(plugin);
26544 
26545 							each(dependencies, function(dep) {
26546 								var defaultSettings = {
26547 									prefix: 'plugins/',
26548 									resource: dep,
26549 									suffix: '/plugin' + suffix + '.js'
26550 								};
26551 
26552 								dep = PluginManager.createUrl(defaultSettings, dep);
26553 								PluginManager.load(dep.resource, dep);
26554 							});
26555 						} else {
26556 							PluginManager.load(plugin, {
26557 								prefix: 'plugins/',
26558 								resource: plugin,
26559 								suffix: '/plugin' + suffix + '.js'
26560 							});
26561 						}
26562 					}
26563 				});
26564 
26565 				scriptLoader.loadQueue(function() {
26566 					if (!self.removed) {
26567 						self.init();
26568 					}
26569 				});
26570 			}
26571 
26572 			loadScripts();
26573 		},
26574 
26575 		/**
26576 		 * Initializes the editor this will be called automatically when
26577 		 * all plugins/themes and language packs are loaded by the rendered method.
26578 		 * This method will setup the iframe and create the theme and plugin instances.
26579 		 *
26580 		 * @method init
26581 		 */
26582 		init: function() {
26583 			var self = this, settings = self.settings, elm = self.getElement();
26584 			var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
26585 
26586 			self.rtl = this.editorManager.i18n.rtl;
26587 			self.editorManager.add(self);
26588 
26589 			settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
26590 
26591 			/**
26592 			 * Reference to the theme instance that was used to generate the UI.
26593 			 *
26594 			 * @property theme
26595 			 * @type tinymce.Theme
26596 			 * @example
26597 			 * // Executes a method on the theme directly
26598 			 * tinymce.activeEditor.theme.someMethod();
26599 			 */
26600 			if (settings.theme) {
26601 				if (typeof settings.theme != "function") {
26602 					settings.theme = settings.theme.replace(/-/, '');
26603 					Theme = ThemeManager.get(settings.theme);
26604 					self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
26605 
26606 					if (self.theme.init) {
26607 						self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$);
26608 					}
26609 				} else {
26610 					self.theme = settings.theme;
26611 				}
26612 			}
26613 
26614 			function initPlugin(plugin) {
26615 				var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
26616 
26617 				pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
26618 				plugin = trim(plugin);
26619 				if (Plugin && inArray(initializedPlugins, plugin) === -1) {
26620 					each(PluginManager.dependencies(plugin), function(dep) {
26621 						initPlugin(dep);
26622 					});
26623 
26624 					pluginInstance = new Plugin(self, pluginUrl, self.$);
26625 
26626 					self.plugins[plugin] = pluginInstance;
26627 
26628 					if (pluginInstance.init) {
26629 						pluginInstance.init(self, pluginUrl);
26630 						initializedPlugins.push(plugin);
26631 					}
26632 				}
26633 			}
26634 
26635 			// Create all plugins
26636 			each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
26637 
26638 			// Measure box
26639 			if (settings.render_ui && self.theme) {
26640 				self.orgDisplay = elm.style.display;
26641 
26642 				if (typeof settings.theme != "function") {
26643 					w = settings.width || elm.style.width || elm.offsetWidth;
26644 					h = settings.height || elm.style.height || elm.offsetHeight;
26645 					minHeight = settings.min_height || 100;
26646 					re = /^[0-9\.]+(|px)$/i;
26647 
26648 					if (re.test('' + w)) {
26649 						w = Math.max(parseInt(w, 10), 100);
26650 					}
26651 
26652 					if (re.test('' + h)) {
26653 						h = Math.max(parseInt(h, 10), minHeight);
26654 					}
26655 
26656 					// Render UI
26657 					o = self.theme.renderUI({
26658 						targetNode: elm,
26659 						width: w,
26660 						height: h,
26661 						deltaWidth: settings.delta_width,
26662 						deltaHeight: settings.delta_height
26663 					});
26664 
26665 					// Resize editor
26666 					if (!settings.content_editable) {
26667 						h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
26668 						if (h < minHeight) {
26669 							h = minHeight;
26670 						}
26671 					}
26672 				} else {
26673 					o = settings.theme(self, elm);
26674 
26675 					// Convert element type to id:s
26676 					if (o.editorContainer.nodeType) {
26677 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
26678 					}
26679 
26680 					// Convert element type to id:s
26681 					if (o.iframeContainer.nodeType) {
26682 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
26683 					}
26684 
26685 					// Use specified iframe height or the targets offsetHeight
26686 					h = o.iframeHeight || elm.offsetHeight;
26687 				}
26688 
26689 				self.editorContainer = o.editorContainer;
26690 			}
26691 
26692 			// Load specified content CSS last
26693 			if (settings.content_css) {
26694 				each(explode(settings.content_css), function(u) {
26695 					self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
26696 				});
26697 			}
26698 
26699 			// Load specified content CSS last
26700 			if (settings.content_style) {
26701 				self.contentStyles.push(settings.content_style);
26702 			}
26703 
26704 			// Content editable mode ends here
26705 			if (settings.content_editable) {
26706 				elm = n = o = null; // Fix IE leak
26707 				return self.initContentBody();
26708 			}
26709 
26710 			self.iframeHTML = settings.doctype + '<html><head>';
26711 
26712 			// We only need to override paths if we have to
26713 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
26714 			if (settings.document_base_url != self.documentBaseUrl) {
26715 				self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
26716 			}
26717 
26718 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
26719 			if (!Env.caretAfter && settings.ie7_compat) {
26720 				self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
26721 			}
26722 
26723 			self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
26724 
26725 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
26726 			for (i = 0; i < self.contentCSS.length; i++) {
26727 				var cssUrl = self.contentCSS[i];
26728 				self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
26729 				self.loadedCSS[cssUrl] = true;
26730 			}
26731 
26732 			bodyId = settings.body_id || 'tinymce';
26733 			if (bodyId.indexOf('=') != -1) {
26734 				bodyId = self.getParam('body_id', '', 'hash');
26735 				bodyId = bodyId[self.id] || bodyId;
26736 			}
26737 
26738 			bodyClass = settings.body_class || '';
26739 			if (bodyClass.indexOf('=') != -1) {
26740 				bodyClass = self.getParam('body_class', '', 'hash');
26741 				bodyClass = bodyClass[self.id] || '';
26742 			}
26743 
26744 			if (settings.content_security_policy) {
26745 				self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />';
26746 			}
26747 
26748 			self.iframeHTML += '</head><body id="' + bodyId +
26749 				'" class="mce-content-body ' + bodyClass +
26750 				'" data-id="' + self.id + '"><br></body></html>';
26751 
26752 			/*eslint no-script-url:0 */
26753 			var domainRelaxUrl = 'javascript:(function(){' +
26754 				'document.open();document.domain="' + document.domain + '";' +
26755 				'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
26756 				'document.close();ed.initContentBody(true);})()';
26757 
26758 			// Domain relaxing is required since the user has messed around with document.domain
26759 			if (document.domain != location.hostname) {
26760 				url = domainRelaxUrl;
26761 			}
26762 
26763 			// Create iframe
26764 			// TODO: ACC add the appropriate description on this.
26765 			var ifr = DOM.create('iframe', {
26766 				id: self.id + "_ifr",
26767 				//src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
26768 				frameBorder: '0',
26769 				allowTransparency: "true",
26770 				title: self.editorManager.translate(
26771 						"Rich Text Area. Press ALT-F9 for menu. " +
26772 						"Press ALT-F10 for toolbar. Press ALT-0 for help"
26773 				),
26774 				style: {
26775 					width: '100%',
26776 					height: h,
26777 					display: 'block' // Important for Gecko to render the iframe correctly
26778 				}
26779 			});
26780 
26781 			ifr.onload = function() {
26782 				ifr.onload = null;
26783 				self.fire("load");
26784 			};
26785 
26786 			DOM.setAttrib(ifr, "src", url || 'javascript:""');
26787 
26788 			self.contentAreaContainer = o.iframeContainer;
26789 			self.iframeElement = ifr;
26790 
26791 			n = DOM.add(o.iframeContainer, ifr);
26792 
26793 			// Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
26794 			// Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
26795 			if (ie) {
26796 				try {
26797 					self.getDoc();
26798 				} catch (e) {
26799 					n.src = url = domainRelaxUrl;
26800 				}
26801 			}
26802 
26803 			if (o.editorContainer) {
26804 				DOM.get(o.editorContainer).style.display = self.orgDisplay;
26805 				self.hidden = DOM.isHidden(o.editorContainer);
26806 			}
26807 
26808 			self.getElement().style.display = 'none';
26809 			DOM.setAttrib(self.id, 'aria-hidden', true);
26810 
26811 			if (!url) {
26812 				self.initContentBody();
26813 			}
26814 
26815 			elm = n = o = null; // Cleanup
26816 		},
26817 
26818 		/**
26819 		 * This method get called by the init method ones the iframe is loaded.
26820 		 * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
26821 		 *
26822 		 * @method initContentBody
26823 		 * @private
26824 		 */
26825 		initContentBody: function(skipWrite) {
26826 			var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;
26827 
26828 			// Restore visibility on target element
26829 			if (!settings.inline) {
26830 				self.getElement().style.visibility = self.orgVisibility;
26831 			}
26832 
26833 			// Setup iframe body
26834 			if (!skipWrite && !settings.content_editable) {
26835 				doc.open();
26836 				doc.write(self.iframeHTML);
26837 				doc.close();
26838 			}
26839 
26840 			if (settings.content_editable) {
26841 				self.on('remove', function() {
26842 					var bodyEl = this.getBody();
26843 
26844 					DOM.removeClass(bodyEl, 'mce-content-body');
26845 					DOM.removeClass(bodyEl, 'mce-edit-focus');
26846 					DOM.setAttrib(bodyEl, 'contentEditable', null);
26847 				});
26848 
26849 				DOM.addClass(targetElm, 'mce-content-body');
26850 				self.contentDocument = doc = settings.content_document || document;
26851 				self.contentWindow = settings.content_window || window;
26852 				self.bodyElement = targetElm;
26853 
26854 				// Prevent leak in IE
26855 				settings.content_document = settings.content_window = null;
26856 
26857 				// TODO: Fix this
26858 				settings.root_name = targetElm.nodeName.toLowerCase();
26859 			}
26860 
26861 			// It will not steal focus while setting contentEditable
26862 			body = self.getBody();
26863 			body.disabled = true;
26864 
26865 			if (!settings.readonly) {
26866 				if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
26867 					body.style.position = 'relative';
26868 				}
26869 
26870 				body.contentEditable = self.getParam('content_editable_state', true);
26871 			}
26872 
26873 			body.disabled = false;
26874 
26875 			/**
26876 			 * Schema instance, enables you to validate elements and it's children.
26877 			 *
26878 			 * @property schema
26879 			 * @type tinymce.html.Schema
26880 			 */
26881 			self.schema = new Schema(settings);
26882 
26883 			/**
26884 			 * DOM instance for the editor.
26885 			 *
26886 			 * @property dom
26887 			 * @type tinymce.dom.DOMUtils
26888 			 * @example
26889 			 * // Adds a class to all paragraphs within the editor
26890 			 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
26891 			 */
26892 			self.dom = new DOMUtils(doc, {
26893 				keep_values: true,
26894 				url_converter: self.convertURL,
26895 				url_converter_scope: self,
26896 				hex_colors: settings.force_hex_style_colors,
26897 				class_filter: settings.class_filter,
26898 				update_styles: true,
26899 				root_element: self.inline ? self.getBody() : null,
26900 				collect: settings.content_editable,
26901 				schema: self.schema,
26902 				onSetAttrib: function(e) {
26903 					self.fire('SetAttrib', e);
26904 				}
26905 			});
26906 
26907 			/**
26908 			 * HTML parser will be used when contents is inserted into the editor.
26909 			 *
26910 			 * @property parser
26911 			 * @type tinymce.html.DomParser
26912 			 */
26913 			self.parser = new DomParser(settings, self.schema);
26914 
26915 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
26916 			self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
26917 				var i = nodes.length, node, dom = self.dom, value, internalName;
26918 
26919 				while (i--) {
26920 					node = nodes[i];
26921 					value = node.attr(name);
26922 					internalName = 'data-mce-' + name;
26923 
26924 					// Add internal attribute if we need to we don't on a refresh of the document
26925 					if (!node.attributes.map[internalName]) {
26926 						if (name === "style") {
26927 							value = dom.serializeStyle(dom.parseStyle(value), node.name);
26928 
26929 							if (!value.length) {
26930 								value = null;
26931 							}
26932 
26933 							node.attr(internalName, value);
26934 							node.attr(name, value);
26935 						} else if (name === "tabindex") {
26936 							node.attr(internalName, value);
26937 							node.attr(name, null);
26938 						} else {
26939 							node.attr(internalName, self.convertURL(value, name, node.name));
26940 						}
26941 					}
26942 				}
26943 			});
26944 
26945 			// Keep scripts from executing
26946 			self.parser.addNodeFilter('script', function(nodes) {
26947 				var i = nodes.length, node;
26948 
26949 				while (i--) {
26950 					node = nodes[i];
26951 					node.attr('type', 'mce-' + (node.attr('type') || 'no/type'));
26952 				}
26953 			});
26954 
26955 			self.parser.addNodeFilter('#cdata', function(nodes) {
26956 				var i = nodes.length, node;
26957 
26958 				while (i--) {
26959 					node = nodes[i];
26960 					node.type = 8;
26961 					node.name = '#comment';
26962 					node.value = '[CDATA[' + node.value + ']]';
26963 				}
26964 			});
26965 
26966 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
26967 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
26968 
26969 				while (i--) {
26970 					node = nodes[i];
26971 
26972 					if (node.isEmpty(nonEmptyElements)) {
26973 						node.append(new Node('br', 1)).shortEnded = true;
26974 					}
26975 				}
26976 			});
26977 
26978 			/**
26979 			 * DOM serializer for the editor. Will be used when contents is extracted from the editor.
26980 			 *
26981 			 * @property serializer
26982 			 * @type tinymce.dom.Serializer
26983 			 * @example
26984 			 * // Serializes the first paragraph in the editor into a string
26985 			 * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
26986 			 */
26987 			self.serializer = new DomSerializer(settings, self);
26988 
26989 			/**
26990 			 * Selection instance for the editor.
26991 			 *
26992 			 * @property selection
26993 			 * @type tinymce.dom.Selection
26994 			 * @example
26995 			 * // Sets some contents to the current selection in the editor
26996 			 * tinymce.activeEditor.selection.setContent('Some contents');
26997 			 *
26998 			 * // Gets the current selection
26999 			 * alert(tinymce.activeEditor.selection.getContent());
27000 			 *
27001 			 * // Selects the first paragraph found
27002 			 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
27003 			 */
27004 			self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
27005 
27006 			/**
27007 			 * Formatter instance.
27008 			 *
27009 			 * @property formatter
27010 			 * @type tinymce.Formatter
27011 			 */
27012 			self.formatter = new Formatter(self);
27013 
27014 			/**
27015 			 * Undo manager instance, responsible for handling undo levels.
27016 			 *
27017 			 * @property undoManager
27018 			 * @type tinymce.UndoManager
27019 			 * @example
27020 			 * // Undoes the last modification to the editor
27021 			 * tinymce.activeEditor.undoManager.undo();
27022 			 */
27023 			self.undoManager = new UndoManager(self);
27024 
27025 			self.forceBlocks = new ForceBlocks(self);
27026 			self.enterKey = new EnterKey(self);
27027 			self.editorCommands = new EditorCommands(self);
27028 			self._nodeChangeDispatcher = new NodeChange(self);
27029 
27030 			self.fire('PreInit');
27031 
27032 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
27033 				doc.body.spellcheck = false; // Gecko
27034 				DOM.setAttrib(body, "spellcheck", "false");
27035 			}
27036 
27037 			self.fire('PostRender');
27038 
27039 			self.quirks = new Quirks(self);
27040 
27041 			if (settings.directionality) {
27042 				body.dir = settings.directionality;
27043 			}
27044 
27045 			if (settings.nowrap) {
27046 				body.style.whiteSpace = "nowrap";
27047 			}
27048 
27049 			if (settings.protect) {
27050 				self.on('BeforeSetContent', function(e) {
27051 					each(settings.protect, function(pattern) {
27052 						e.content = e.content.replace(pattern, function(str) {
27053 							return '<!--mce:protected ' + escape(str) + '-->';
27054 						});
27055 					});
27056 				});
27057 			}
27058 
27059 			self.on('SetContent', function() {
27060 				self.addVisual(self.getBody());
27061 			});
27062 
27063 			// Remove empty contents
27064 			if (settings.padd_empty_editor) {
27065 				self.on('PostProcess', function(e) {
27066 					e.content = e.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
27067 				});
27068 			}
27069 
27070 			self.load({initial: true, format: 'html'});
27071 			self.startContent = self.getContent({format: 'raw'});
27072 
27073 			/**
27074 			 * Is set to true after the editor instance has been initialized
27075 			 *
27076 			 * @property initialized
27077 			 * @type Boolean
27078 			 * @example
27079 			 * function isEditorInitialized(editor) {
27080 			 *     return editor && editor.initialized;
27081 			 * }
27082 			 */
27083 			self.initialized = true;
27084 			self.bindPendingEventDelegates();
27085 
27086 			self.fire('init');
27087 			self.focus(true);
27088 			self.nodeChanged({initial: true});
27089 			self.execCallback('init_instance_callback', self);
27090 
27091 			// Add editor specific CSS styles
27092 			if (self.contentStyles.length > 0) {
27093 				contentCssText = '';
27094 
27095 				each(self.contentStyles, function(style) {
27096 					contentCssText += style + "\r\n";
27097 				});
27098 
27099 				self.dom.addStyle(contentCssText);
27100 			}
27101 
27102 			// Load specified content CSS last
27103 			each(self.contentCSS, function(cssUrl) {
27104 				if (!self.loadedCSS[cssUrl]) {
27105 					self.dom.loadCSS(cssUrl);
27106 					self.loadedCSS[cssUrl] = true;
27107 				}
27108 			});
27109 
27110 			// Handle auto focus
27111 			if (settings.auto_focus) {
27112 				setTimeout(function() {
27113 					var editor;
27114 
27115 					if (settings.auto_focus === true) {
27116 						editor = self;
27117 					} else {
27118 						editor = self.editorManager.get(settings.auto_focus);
27119 					}
27120 
27121 					editor.focus();
27122 				}, 100);
27123 			}
27124 
27125 			// Clean up references for IE
27126 			targetElm = doc = body = null;
27127 		},
27128 
27129 		/**
27130 		 * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
27131 		 * it will also place DOM focus inside the editor.
27132 		 *
27133 		 * @method focus
27134 		 * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
27135 		 */
27136 		focus: function(skipFocus) {
27137 			var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
27138 			var controlElm, doc = self.getDoc(), body;
27139 
27140 			if (!skipFocus) {
27141 				// Get selected control element
27142 				rng = selection.getRng();
27143 				if (rng.item) {
27144 					controlElm = rng.item(0);
27145 				}
27146 
27147 				self._refreshContentEditable();
27148 
27149 				// Focus the window iframe
27150 				if (!contentEditable) {
27151 					// WebKit needs this call to fire focusin event properly see #5948
27152 					// But Opera pre Blink engine will produce an empty selection so skip Opera
27153 					if (!Env.opera) {
27154 						self.getBody().focus();
27155 					}
27156 
27157 					self.getWin().focus();
27158 				}
27159 
27160 				// Focus the body as well since it's contentEditable
27161 				if (isGecko || contentEditable) {
27162 					body = self.getBody();
27163 
27164 					// Check for setActive since it doesn't scroll to the element
27165 					if (body.setActive) {
27166 						// IE 11 sometimes throws "Invalid function" then fallback to focus
27167 						try {
27168 							body.setActive();
27169 						} catch (ex) {
27170 							body.focus();
27171 						}
27172 					} else {
27173 						body.focus();
27174 					}
27175 
27176 					if (contentEditable) {
27177 						selection.normalize();
27178 					}
27179 				}
27180 
27181 				// Restore selected control element
27182 				// This is needed when for example an image is selected within a
27183 				// layer a call to focus will then remove the control selection
27184 				if (controlElm && controlElm.ownerDocument == doc) {
27185 					rng = doc.body.createControlRange();
27186 					rng.addElement(controlElm);
27187 					rng.select();
27188 				}
27189 			}
27190 
27191 			self.editorManager.setActive(self);
27192 		},
27193 
27194 		/**
27195 		 * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
27196 		 * There new event model is a better way to add callback so this method might be removed in the future.
27197 		 *
27198 		 * @method execCallback
27199 		 * @param {String} name Name of the callback to execute.
27200 		 * @return {Object} Return value passed from callback function.
27201 		 */
27202 		execCallback: function(name) {
27203 			var self = this, callback = self.settings[name], scope;
27204 
27205 			if (!callback) {
27206 				return;
27207 			}
27208 
27209 			// Look through lookup
27210 			if (self.callbackLookup && (scope = self.callbackLookup[name])) {
27211 				callback = scope.func;
27212 				scope = scope.scope;
27213 			}
27214 
27215 			if (typeof(callback) === 'string') {
27216 				scope = callback.replace(/\.\w+$/, '');
27217 				scope = scope ? resolve(scope) : 0;
27218 				callback = resolve(callback);
27219 				self.callbackLookup = self.callbackLookup || {};
27220 				self.callbackLookup[name] = {func: callback, scope: scope};
27221 			}
27222 
27223 			return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
27224 		},
27225 
27226 		/**
27227 		 * Translates the specified string by replacing variables with language pack items it will also check if there is
27228 		 * a key mathcin the input.
27229 		 *
27230 		 * @method translate
27231 		 * @param {String} text String to translate by the language pack data.
27232 		 * @return {String} Translated string.
27233 		 */
27234 		translate: function(text) {
27235 			var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
27236 
27237 			if (!text) {
27238 				return '';
27239 			}
27240 
27241 			return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
27242 				return i18n.data[lang + '.' + b] || '{#' + b + '}';
27243 			});
27244 		},
27245 
27246 		/**
27247 		 * Returns a language pack item by name/key.
27248 		 *
27249 		 * @method getLang
27250 		 * @param {String} name Name/key to get from the language pack.
27251 		 * @param {String} defaultVal Optional default value to retrive.
27252 		 */
27253 		getLang: function(name, defaultVal) {
27254 			return (
27255 				this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
27256 				(defaultVal !== undefined ? defaultVal : '{#' + name + '}')
27257 			);
27258 		},
27259 
27260 		/**
27261 		 * Returns a configuration parameter by name.
27262 		 *
27263 		 * @method getParam
27264 		 * @param {String} name Configruation parameter to retrive.
27265 		 * @param {String} defaultVal Optional default value to return.
27266 		 * @param {String} type Optional type parameter.
27267 		 * @return {String} Configuration parameter value or default value.
27268 		 * @example
27269 		 * // Returns a specific config value from the currently active editor
27270 		 * var someval = tinymce.activeEditor.getParam('myvalue');
27271 		 *
27272 		 * // Returns a specific config value from a specific editor instance by id
27273 		 * var someval2 = tinymce.get('my_editor').getParam('myvalue');
27274 		 */
27275 		getParam: function(name, defaultVal, type) {
27276 			var value = name in this.settings ? this.settings[name] : defaultVal, output;
27277 
27278 			if (type === 'hash') {
27279 				output = {};
27280 
27281 				if (typeof(value) === 'string') {
27282 					each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
27283 						value = value.split('=');
27284 
27285 						if (value.length > 1) {
27286 							output[trim(value[0])] = trim(value[1]);
27287 						} else {
27288 							output[trim(value[0])] = trim(value);
27289 						}
27290 					});
27291 				} else {
27292 					output = value;
27293 				}
27294 
27295 				return output;
27296 			}
27297 
27298 			return value;
27299 		},
27300 
27301 		/**
27302 		 * Distpaches out a onNodeChange event to all observers. This method should be called when you
27303 		 * need to update the UI states or element path etc.
27304 		 *
27305 		 * @method nodeChanged
27306 		 * @param {Object} args Optional args to pass to NodeChange event handlers.
27307 		 */
27308 		nodeChanged: function(args) {
27309 			this._nodeChangeDispatcher.nodeChanged(args);
27310 		},
27311 
27312 		/**
27313 		 * Adds a button that later gets created by the theme in the editors toolbars.
27314 		 *
27315 		 * @method addButton
27316 		 * @param {String} name Button name to add.
27317 		 * @param {Object} settings Settings object with title, cmd etc.
27318 		 * @example
27319 		 * // Adds a custom button to the editor that inserts contents when clicked
27320 		 * tinymce.init({
27321 		 *    ...
27322 		 *
27323 		 *    toolbar: 'example'
27324 		 *
27325 		 *    setup: function(ed) {
27326 		 *       ed.addButton('example', {
27327 		 *          title: 'My title',
27328 		 *          image: '../js/tinymce/plugins/example/img/example.gif',
27329 		 *          onclick: function() {
27330 		 *             ed.insertContent('Hello world!!');
27331 		 *          }
27332 		 *       });
27333 		 *    }
27334 		 * });
27335 		 */
27336 		addButton: function(name, settings) {
27337 			var self = this;
27338 
27339 			if (settings.cmd) {
27340 				settings.onclick = function() {
27341 					self.execCommand(settings.cmd);
27342 				};
27343 			}
27344 
27345 			if (!settings.text && !settings.icon) {
27346 				settings.icon = name;
27347 			}
27348 
27349 			self.buttons = self.buttons || {};
27350 			settings.tooltip = settings.tooltip || settings.title;
27351 			self.buttons[name] = settings;
27352 		},
27353 
27354 		/**
27355 		 * Adds a menu item to be used in the menus of the theme. There might be multiple instances
27356 		 * of this menu item for example it might be used in the main menus of the theme but also in
27357 		 * the context menu so make sure that it's self contained and supports multiple instances.
27358 		 *
27359 		 * @method addMenuItem
27360 		 * @param {String} name Menu item name to add.
27361 		 * @param {Object} settings Settings object with title, cmd etc.
27362 		 * @example
27363 		 * // Adds a custom menu item to the editor that inserts contents when clicked
27364 		 * // The context option allows you to add the menu item to an existing default menu
27365 		 * tinymce.init({
27366 		 *    ...
27367 		 *
27368 		 *    setup: function(ed) {
27369 		 *       ed.addMenuItem('example', {
27370 		 *          text: 'My menu item',
27371 		 *          context: 'tools',
27372 		 *          onclick: function() {
27373 		 *             ed.insertContent('Hello world!!');
27374 		 *          }
27375 		 *       });
27376 		 *    }
27377 		 * });
27378 		 */
27379 		addMenuItem: function(name, settings) {
27380 			var self = this;
27381 
27382 			if (settings.cmd) {
27383 				settings.onclick = function() {
27384 					self.execCommand(settings.cmd);
27385 				};
27386 			}
27387 
27388 			self.menuItems = self.menuItems || {};
27389 			self.menuItems[name] = settings;
27390 		},
27391 
27392 		/**
27393 		 * Adds a custom command to the editor, you can also override existing commands with this method.
27394 		 * The command that you add can be executed with execCommand.
27395 		 *
27396 		 * @method addCommand
27397 		 * @param {String} name Command name to add/override.
27398 		 * @param {addCommandCallback} callback Function to execute when the command occurs.
27399 		 * @param {Object} scope Optional scope to execute the function in.
27400 		 * @example
27401 		 * // Adds a custom command that later can be executed using execCommand
27402 		 * tinymce.init({
27403 		 *    ...
27404 		 *
27405 		 *    setup: function(ed) {
27406 		 *       // Register example command
27407 		 *       ed.addCommand('mycommand', function(ui, v) {
27408 		 *          ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
27409 		 *       });
27410 		 *    }
27411 		 * });
27412 		 */
27413 		addCommand: function(name, callback, scope) {
27414 			/**
27415 			 * Callback function that gets called when a command is executed.
27416 			 *
27417 			 * @callback addCommandCallback
27418 			 * @param {Boolean} ui Display UI state true/false.
27419 			 * @param {Object} value Optional value for command.
27420 			 * @return {Boolean} True/false state if the command was handled or not.
27421 			 */
27422 			this.execCommands[name] = {func: callback, scope: scope || this};
27423 		},
27424 
27425 		/**
27426 		 * Adds a custom query state command to the editor, you can also override existing commands with this method.
27427 		 * The command that you add can be executed with queryCommandState function.
27428 		 *
27429 		 * @method addQueryStateHandler
27430 		 * @param {String} name Command name to add/override.
27431 		 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
27432 		 * @param {Object} scope Optional scope to execute the function in.
27433 		 */
27434 		addQueryStateHandler: function(name, callback, scope) {
27435 			/**
27436 			 * Callback function that gets called when a queryCommandState is executed.
27437 			 *
27438 			 * @callback addQueryStateHandlerCallback
27439 			 * @return {Boolean} True/false state if the command is enabled or not like is it bold.
27440 			 */
27441 			this.queryStateCommands[name] = {func: callback, scope: scope || this};
27442 		},
27443 
27444 		/**
27445 		 * Adds a custom query value command to the editor, you can also override existing commands with this method.
27446 		 * The command that you add can be executed with queryCommandValue function.
27447 		 *
27448 		 * @method addQueryValueHandler
27449 		 * @param {String} name Command name to add/override.
27450 		 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
27451 		 * @param {Object} scope Optional scope to execute the function in.
27452 		 */
27453 		addQueryValueHandler: function(name, callback, scope) {
27454 			/**
27455 			 * Callback function that gets called when a queryCommandValue is executed.
27456 			 *
27457 			 * @callback addQueryValueHandlerCallback
27458 			 * @return {Object} Value of the command or undefined.
27459 			 */
27460 			this.queryValueCommands[name] = {func: callback, scope: scope || this};
27461 		},
27462 
27463 		/**
27464 		 * Adds a keyboard shortcut for some command or function.
27465 		 *
27466 		 * @method addShortcut
27467 		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
27468 		 * @param {String} desc Text description for the command.
27469 		 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
27470 		 * @param {Object} sc Optional scope to execute the function in.
27471 		 * @return {Boolean} true/false state if the shortcut was added or not.
27472 		 */
27473 		addShortcut: function(pattern, desc, cmdFunc, scope) {
27474 			this.shortcuts.add(pattern, desc, cmdFunc, scope);
27475 		},
27476 
27477 		/**
27478 		 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
27479 		 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
27480 		 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
27481 		 * return true it will handle the command as a internal browser command.
27482 		 *
27483 		 * @method execCommand
27484 		 * @param {String} cmd Command name to execute, for example mceLink or Bold.
27485 		 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
27486 		 * @param {mixed} value Optional command value, this can be anything.
27487 		 * @param {Object} a Optional arguments object.
27488 		 */
27489 		execCommand: function(cmd, ui, value, args) {
27490 			var self = this, state = 0, cmdItem;
27491 
27492 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
27493 				self.focus();
27494 			}
27495 
27496 			args = extend({}, args);
27497 			args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
27498 			if (args.isDefaultPrevented()) {
27499 				return false;
27500 			}
27501 
27502 			// Registred commands
27503 			if ((cmdItem = self.execCommands[cmd])) {
27504 				// Fall through on true
27505 				if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
27506 					self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27507 					return true;
27508 				}
27509 			}
27510 
27511 			// Plugin commands
27512 			each(self.plugins, function(p) {
27513 				if (p.execCommand && p.execCommand(cmd, ui, value)) {
27514 					self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27515 					state = true;
27516 					return false;
27517 				}
27518 			});
27519 
27520 			if (state) {
27521 				return state;
27522 			}
27523 
27524 			// Theme commands
27525 			if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
27526 				self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27527 				return true;
27528 			}
27529 
27530 			// Editor commands
27531 			if (self.editorCommands.execCommand(cmd, ui, value)) {
27532 				self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27533 				return true;
27534 			}
27535 
27536 			// Browser commands
27537 			try {
27538 				state = self.getDoc().execCommand(cmd, ui, value);
27539 			} catch (ex) {
27540 				// Ignore old IE errors
27541 			}
27542 
27543 			if (state) {
27544 				self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27545 				return true;
27546 			}
27547 
27548 			return false;
27549 		},
27550 
27551 		/**
27552 		 * Returns a command specific state, for example if bold is enabled or not.
27553 		 *
27554 		 * @method queryCommandState
27555 		 * @param {string} cmd Command to query state from.
27556 		 * @return {Boolean} Command specific state, for example if bold is enabled or not.
27557 		 */
27558 		queryCommandState: function(cmd) {
27559 			var self = this, queryItem, returnVal;
27560 
27561 			// Is hidden then return undefined
27562 			if (self._isHidden()) {
27563 				return;
27564 			}
27565 
27566 			// Registred commands
27567 			if ((queryItem = self.queryStateCommands[cmd])) {
27568 				returnVal = queryItem.func.call(queryItem.scope);
27569 
27570 				// Fall though on non boolean returns
27571 				if (returnVal === true || returnVal === false) {
27572 					return returnVal;
27573 				}
27574 			}
27575 
27576 			// Editor commands
27577 			returnVal = self.editorCommands.queryCommandState(cmd);
27578 			if (returnVal !== -1) {
27579 				return returnVal;
27580 			}
27581 
27582 			// Browser commands
27583 			try {
27584 				return self.getDoc().queryCommandState(cmd);
27585 			} catch (ex) {
27586 				// Fails sometimes see bug: 1896577
27587 			}
27588 		},
27589 
27590 		/**
27591 		 * Returns a command specific value, for example the current font size.
27592 		 *
27593 		 * @method queryCommandValue
27594 		 * @param {string} cmd Command to query value from.
27595 		 * @return {Object} Command specific value, for example the current font size.
27596 		 */
27597 		queryCommandValue: function(cmd) {
27598 			var self = this, queryItem, returnVal;
27599 
27600 			// Is hidden then return undefined
27601 			if (self._isHidden()) {
27602 				return;
27603 			}
27604 
27605 			// Registred commands
27606 			if ((queryItem = self.queryValueCommands[cmd])) {
27607 				returnVal = queryItem.func.call(queryItem.scope);
27608 
27609 				// Fall though on true
27610 				if (returnVal !== true) {
27611 					return returnVal;
27612 				}
27613 			}
27614 
27615 			// Editor commands
27616 			returnVal = self.editorCommands.queryCommandValue(cmd);
27617 			if (returnVal !== undefined) {
27618 				return returnVal;
27619 			}
27620 
27621 			// Browser commands
27622 			try {
27623 				return self.getDoc().queryCommandValue(cmd);
27624 			} catch (ex) {
27625 				// Fails sometimes see bug: 1896577
27626 			}
27627 		},
27628 
27629 		/**
27630 		 * Shows the editor and hides any textarea/div that the editor is supposed to replace.
27631 		 *
27632 		 * @method show
27633 		 */
27634 		show: function() {
27635 			var self = this;
27636 
27637 			if (self.hidden) {
27638 				self.hidden = false;
27639 
27640 				if (self.inline) {
27641 					self.getBody().contentEditable = true;
27642 				} else {
27643 					DOM.show(self.getContainer());
27644 					DOM.hide(self.id);
27645 				}
27646 
27647 				self.load();
27648 				self.fire('show');
27649 			}
27650 		},
27651 
27652 		/**
27653 		 * Hides the editor and shows any textarea/div that the editor is supposed to replace.
27654 		 *
27655 		 * @method hide
27656 		 */
27657 		hide: function() {
27658 			var self = this, doc = self.getDoc();
27659 
27660 			if (!self.hidden) {
27661 				// Fixed bug where IE has a blinking cursor left from the editor
27662 				if (ie && doc && !self.inline) {
27663 					doc.execCommand('SelectAll');
27664 				}
27665 
27666 				// We must save before we hide so Safari doesn't crash
27667 				self.save();
27668 
27669 				if (self.inline) {
27670 					self.getBody().contentEditable = false;
27671 
27672 					// Make sure the editor gets blurred
27673 					if (self == self.editorManager.focusedEditor) {
27674 						self.editorManager.focusedEditor = null;
27675 					}
27676 				} else {
27677 					DOM.hide(self.getContainer());
27678 					DOM.setStyle(self.id, 'display', self.orgDisplay);
27679 				}
27680 
27681 				self.hidden = true;
27682 				self.fire('hide');
27683 			}
27684 		},
27685 
27686 		/**
27687 		 * Returns true/false if the editor is hidden or not.
27688 		 *
27689 		 * @method isHidden
27690 		 * @return {Boolean} True/false if the editor is hidden or not.
27691 		 */
27692 		isHidden: function() {
27693 			return !!this.hidden;
27694 		},
27695 
27696 		/**
27697 		 * Sets the progress state, this will display a throbber/progess for the editor.
27698 		 * This is ideal for asycronous operations like an AJAX save call.
27699 		 *
27700 		 * @method setProgressState
27701 		 * @param {Boolean} state Boolean state if the progress should be shown or hidden.
27702 		 * @param {Number} time Optional time to wait before the progress gets shown.
27703 		 * @return {Boolean} Same as the input state.
27704 		 * @example
27705 		 * // Show progress for the active editor
27706 		 * tinymce.activeEditor.setProgressState(true);
27707 		 *
27708 		 * // Hide progress for the active editor
27709 		 * tinymce.activeEditor.setProgressState(false);
27710 		 *
27711 		 * // Show progress after 3 seconds
27712 		 * tinymce.activeEditor.setProgressState(true, 3000);
27713 		 */
27714 		setProgressState: function(state, time) {
27715 			this.fire('ProgressState', {state: state, time: time});
27716 		},
27717 
27718 		/**
27719 		 * Loads contents from the textarea or div element that got converted into an editor instance.
27720 		 * This method will move the contents from that textarea or div into the editor by using setContent
27721 		 * so all events etc that method has will get dispatched as well.
27722 		 *
27723 		 * @method load
27724 		 * @param {Object} args Optional content object, this gets passed around through the whole load process.
27725 		 * @return {String} HTML string that got set into the editor.
27726 		 */
27727 		load: function(args) {
27728 			var self = this, elm = self.getElement(), html;
27729 
27730 			if (elm) {
27731 				args = args || {};
27732 				args.load = true;
27733 
27734 				html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
27735 				args.element = elm;
27736 
27737 				if (!args.no_events) {
27738 					self.fire('LoadContent', args);
27739 				}
27740 
27741 				args.element = elm = null;
27742 
27743 				return html;
27744 			}
27745 		},
27746 
27747 		/**
27748 		 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
27749 		 * This method will move the HTML contents from the editor into that textarea or div by getContent
27750 		 * so all events etc that method has will get dispatched as well.
27751 		 *
27752 		 * @method save
27753 		 * @param {Object} args Optional content object, this gets passed around through the whole save process.
27754 		 * @return {String} HTML string that got set into the textarea/div.
27755 		 */
27756 		save: function(args) {
27757 			var self = this, elm = self.getElement(), html, form;
27758 
27759 			if (!elm || !self.initialized) {
27760 				return;
27761 			}
27762 
27763 			args = args || {};
27764 			args.save = true;
27765 
27766 			args.element = elm;
27767 			html = args.content = self.getContent(args);
27768 
27769 			if (!args.no_events) {
27770 				self.fire('SaveContent', args);
27771 			}
27772 
27773 			html = args.content;
27774 
27775 			if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
27776 				// Update DIV element when not in inline mode
27777 				if (!self.inline) {
27778 					elm.innerHTML = html;
27779 				}
27780 
27781 				// Update hidden form element
27782 				if ((form = DOM.getParent(self.id, 'form'))) {
27783 					each(form.elements, function(elm) {
27784 						if (elm.name == self.id) {
27785 							elm.value = html;
27786 							return false;
27787 						}
27788 					});
27789 				}
27790 			} else {
27791 				elm.value = html;
27792 			}
27793 
27794 			args.element = elm = null;
27795 
27796 			if (args.set_dirty !== false) {
27797 				self.isNotDirty = true;
27798 			}
27799 
27800 			return html;
27801 		},
27802 
27803 		/**
27804 		 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
27805 		 * the different cleanup rules options.
27806 		 *
27807 		 * @method setContent
27808 		 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
27809 		 * @param {Object} args Optional content object, this gets passed around through the whole set process.
27810 		 * @return {String} HTML string that got set into the editor.
27811 		 * @example
27812 		 * // Sets the HTML contents of the activeEditor editor
27813 		 * tinymce.activeEditor.setContent('<span>some</span> html');
27814 		 *
27815 		 * // Sets the raw contents of the activeEditor editor
27816 		 * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
27817 		 *
27818 		 * // Sets the content of a specific editor (my_editor in this example)
27819 		 * tinymce.get('my_editor').setContent(data);
27820 		 *
27821 		 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
27822 		 * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
27823 		 */
27824 		setContent: function(content, args) {
27825 			var self = this, body = self.getBody(), forcedRootBlockName;
27826 
27827 			// Setup args object
27828 			args = args || {};
27829 			args.format = args.format || 'html';
27830 			args.set = true;
27831 			args.content = content;
27832 
27833 			// Do preprocessing
27834 			if (!args.no_events) {
27835 				self.fire('BeforeSetContent', args);
27836 			}
27837 
27838 			content = args.content;
27839 
27840 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
27841 			// It will also be impossible to place the caret in the editor unless there is a BR element present
27842 			if (content.length === 0 || /^\s+$/.test(content)) {
27843 				forcedRootBlockName = self.settings.forced_root_block;
27844 
27845 				// Check if forcedRootBlock is configured and that the block is a valid child of the body
27846 				if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
27847 					// Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
27848 					content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
27849 					content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
27850 				} else if (!ie) {
27851 					// We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
27852 					content = '<br data-mce-bogus="1">';
27853 				}
27854 
27855 				self.dom.setHTML(body, content);
27856 
27857 				self.fire('SetContent', args);
27858 			} else {
27859 				// Parse and serialize the html
27860 				if (args.format !== 'raw') {
27861 					content = new Serializer({}, self.schema).serialize(
27862 						self.parser.parse(content, {isRootContent: true})
27863 					);
27864 				}
27865 
27866 				// Set the new cleaned contents to the editor
27867 				args.content = trim(content);
27868 				self.dom.setHTML(body, args.content);
27869 
27870 				// Do post processing
27871 				if (!args.no_events) {
27872 					self.fire('SetContent', args);
27873 				}
27874 
27875 				// Don't normalize selection if the focused element isn't the body in
27876 				// content editable mode since it will steal focus otherwise
27877 				/*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
27878 					self.selection.normalize();
27879 				}*/
27880 			}
27881 
27882 			return args.content;
27883 		},
27884 
27885 		/**
27886 		 * Gets the content from the editor instance, this will cleanup the content before it gets returned using
27887 		 * the different cleanup rules options.
27888 		 *
27889 		 * @method getContent
27890 		 * @param {Object} args Optional content object, this gets passed around through the whole get process.
27891 		 * @return {String} Cleaned content string, normally HTML contents.
27892 		 * @example
27893 		 * // Get the HTML contents of the currently active editor
27894 		 * console.debug(tinymce.activeEditor.getContent());
27895 		 *
27896 		 * // Get the raw contents of the currently active editor
27897 		 * tinymce.activeEditor.getContent({format: 'raw'});
27898 		 *
27899 		 * // Get content of a specific editor:
27900 		 * tinymce.get('content id').getContent()
27901 		 */
27902 		getContent: function(args) {
27903 			var self = this, content, body = self.getBody();
27904 
27905 			// Setup args object
27906 			args = args || {};
27907 			args.format = args.format || 'html';
27908 			args.get = true;
27909 			args.getInner = true;
27910 
27911 			// Do preprocessing
27912 			if (!args.no_events) {
27913 				self.fire('BeforeGetContent', args);
27914 			}
27915 
27916 			// Get raw contents or by default the cleaned contents
27917 			if (args.format == 'raw') {
27918 				content = body.innerHTML;
27919 			} else if (args.format == 'text') {
27920 				content = body.innerText || body.textContent;
27921 			} else {
27922 				content = self.serializer.serialize(body, args);
27923 			}
27924 
27925 			// Trim whitespace in beginning/end of HTML
27926 			if (args.format != 'text') {
27927 				args.content = trim(content);
27928 			} else {
27929 				args.content = content;
27930 			}
27931 
27932 			// Do post processing
27933 			if (!args.no_events) {
27934 				self.fire('GetContent', args);
27935 			}
27936 
27937 			return args.content;
27938 		},
27939 
27940 		/**
27941 		 * Inserts content at caret position.
27942 		 *
27943 		 * @method insertContent
27944 		 * @param {String} content Content to insert.
27945 		 * @param {Object} args Optional args to pass to insert call.
27946 		 */
27947 		insertContent: function(content, args) {
27948 			if (args) {
27949 				content = extend({content: content}, args);
27950 			}
27951 
27952 			this.execCommand('mceInsertContent', false, content);
27953 		},
27954 
27955 		/**
27956 		 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
27957 		 *
27958 		 * @method isDirty
27959 		 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
27960 		 * @example
27961 		 * if (tinymce.activeEditor.isDirty())
27962 		 *     alert("You must save your contents.");
27963 		 */
27964 		isDirty: function() {
27965 			return !this.isNotDirty;
27966 		},
27967 
27968 		/**
27969 		 * Returns the editors container element. The container element wrappes in
27970 		 * all the elements added to the page for the editor. Such as UI, iframe etc.
27971 		 *
27972 		 * @method getContainer
27973 		 * @return {Element} HTML DOM element for the editor container.
27974 		 */
27975 		getContainer: function() {
27976 			var self = this;
27977 
27978 			if (!self.container) {
27979 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
27980 			}
27981 
27982 			return self.container;
27983 		},
27984 
27985 		/**
27986 		 * Returns the editors content area container element. The this element is the one who
27987 		 * holds the iframe or the editable element.
27988 		 *
27989 		 * @method getContentAreaContainer
27990 		 * @return {Element} HTML DOM element for the editor area container.
27991 		 */
27992 		getContentAreaContainer: function() {
27993 			return this.contentAreaContainer;
27994 		},
27995 
27996 		/**
27997 		 * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
27998 		 *
27999 		 * @method getElement
28000 		 * @return {Element} HTML DOM element for the replaced element.
28001 		 */
28002 		getElement: function() {
28003 			if (!this.targetElm) {
28004 				this.targetElm = DOM.get(this.id);
28005 			}
28006 
28007 			return this.targetElm;
28008 		},
28009 
28010 		/**
28011 		 * Returns the iframes window object.
28012 		 *
28013 		 * @method getWin
28014 		 * @return {Window} Iframe DOM window object.
28015 		 */
28016 		getWin: function() {
28017 			var self = this, elm;
28018 
28019 			if (!self.contentWindow) {
28020 				elm = self.iframeElement;
28021 
28022 				if (elm) {
28023 					self.contentWindow = elm.contentWindow;
28024 				}
28025 			}
28026 
28027 			return self.contentWindow;
28028 		},
28029 
28030 		/**
28031 		 * Returns the iframes document object.
28032 		 *
28033 		 * @method getDoc
28034 		 * @return {Document} Iframe DOM document object.
28035 		 */
28036 		getDoc: function() {
28037 			var self = this, win;
28038 
28039 			if (!self.contentDocument) {
28040 				win = self.getWin();
28041 
28042 				if (win) {
28043 					self.contentDocument = win.document;
28044 				}
28045 			}
28046 
28047 			return self.contentDocument;
28048 		},
28049 
28050 		/**
28051 		 * Returns the iframes body element.
28052 		 *
28053 		 * @method getBody
28054 		 * @return {Element} Iframe body element.
28055 		 */
28056 		getBody: function() {
28057 			return this.bodyElement || this.getDoc().body;
28058 		},
28059 
28060 		/**
28061 		 * URL converter function this gets executed each time a user adds an img, a or
28062 		 * any other element that has a URL in it. This will be called both by the DOM and HTML
28063 		 * manipulation functions.
28064 		 *
28065 		 * @method convertURL
28066 		 * @param {string} url URL to convert.
28067 		 * @param {string} name Attribute name src, href etc.
28068 		 * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
28069 		 * @return {string} Converted URL string.
28070 		 */
28071 		convertURL: function(url, name, elm) {
28072 			var self = this, settings = self.settings;
28073 
28074 			// Use callback instead
28075 			if (settings.urlconverter_callback) {
28076 				return self.execCallback('urlconverter_callback', url, elm, true, name);
28077 			}
28078 
28079 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
28080 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
28081 				return url;
28082 			}
28083 
28084 			// Convert to relative
28085 			if (settings.relative_urls) {
28086 				return self.documentBaseURI.toRelative(url);
28087 			}
28088 
28089 			// Convert to absolute
28090 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
28091 
28092 			return url;
28093 		},
28094 
28095 		/**
28096 		 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
28097 		 *
28098 		 * @method addVisual
28099 		 * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
28100 		 */
28101 		addVisual: function(elm) {
28102 			var self = this, settings = self.settings, dom = self.dom, cls;
28103 
28104 			elm = elm || self.getBody();
28105 
28106 			if (self.hasVisual === undefined) {
28107 				self.hasVisual = settings.visual;
28108 			}
28109 
28110 			each(dom.select('table,a', elm), function(elm) {
28111 				var value;
28112 
28113 				switch (elm.nodeName) {
28114 					case 'TABLE':
28115 						cls = settings.visual_table_class || 'mce-item-table';
28116 						value = dom.getAttrib(elm, 'border');
28117 
28118 						if ((!value || value == '0') && self.hasVisual) {
28119 							dom.addClass(elm, cls);
28120 						} else {
28121 							dom.removeClass(elm, cls);
28122 						}
28123 
28124 						return;
28125 
28126 					case 'A':
28127 						if (!dom.getAttrib(elm, 'href', false)) {
28128 							value = dom.getAttrib(elm, 'name') || elm.id;
28129 							cls = settings.visual_anchor_class || 'mce-item-anchor';
28130 
28131 							if (value && self.hasVisual) {
28132 								dom.addClass(elm, cls);
28133 							} else {
28134 								dom.removeClass(elm, cls);
28135 							}
28136 						}
28137 
28138 						return;
28139 				}
28140 			});
28141 
28142 			self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
28143 		},
28144 
28145 		/**
28146 		 * Removes the editor from the dom and tinymce collection.
28147 		 *
28148 		 * @method remove
28149 		 */
28150 		remove: function() {
28151 			var self = this;
28152 
28153 			if (!self.removed) {
28154 				self.save();
28155 				self.removed = 1;
28156 				self.unbindAllNativeEvents();
28157 
28158 				// Remove any hidden input
28159 				if (self.hasHiddenInput) {
28160 					DOM.remove(self.getElement().nextSibling);
28161 				}
28162 
28163 				if (!self.inline) {
28164 					// IE 9 has a bug where the selection stops working if you place the
28165 					// caret inside the editor then remove the iframe
28166 					if (ie && ie < 10) {
28167 						self.getDoc().execCommand('SelectAll', false, null);
28168 					}
28169 
28170 					DOM.setStyle(self.id, 'display', self.orgDisplay);
28171 					self.getBody().onload = null; // Prevent #6816
28172 				}
28173 
28174 				self.fire('remove');
28175 
28176 				self.editorManager.remove(self);
28177 				DOM.remove(self.getContainer());
28178 				self.destroy();
28179 			}
28180 		},
28181 
28182 		/**
28183 		 * Destroys the editor instance by removing all events, element references or other resources
28184 		 * that could leak memory. This method will be called automatically when the page is unloaded
28185 		 * but you can also call it directly if you know what you are doing.
28186 		 *
28187 		 * @method destroy
28188 		 * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
28189 		 */
28190 		destroy: function(automatic) {
28191 			var self = this, form;
28192 
28193 			// One time is enough
28194 			if (self.destroyed) {
28195 				return;
28196 			}
28197 
28198 			// If user manually calls destroy and not remove
28199 			// Users seems to have logic that calls destroy instead of remove
28200 			if (!automatic && !self.removed) {
28201 				self.remove();
28202 				return;
28203 			}
28204 
28205 			if (!automatic) {
28206 				self.editorManager.off('beforeunload', self._beforeUnload);
28207 
28208 				// Manual destroy
28209 				if (self.theme && self.theme.destroy) {
28210 					self.theme.destroy();
28211 				}
28212 
28213 				// Destroy controls, selection and dom
28214 				self.selection.destroy();
28215 				self.dom.destroy();
28216 			}
28217 
28218 			form = self.formElement;
28219 			if (form) {
28220 				if (form._mceOldSubmit) {
28221 					form.submit = form._mceOldSubmit;
28222 					form._mceOldSubmit = null;
28223 				}
28224 
28225 				DOM.unbind(form, 'submit reset', self.formEventDelegate);
28226 			}
28227 
28228 			self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
28229 			self.bodyElement = self.contentDocument = self.contentWindow = null;
28230 			self.iframeElement = self.targetElm = null;
28231 
28232 			if (self.selection) {
28233 				self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
28234 			}
28235 
28236 			self.destroyed = 1;
28237 		},
28238 
28239 		// Internal functions
28240 
28241 		_refreshContentEditable: function() {
28242 			var self = this, body, parent;
28243 
28244 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
28245 			if (self._isHidden()) {
28246 				body = self.getBody();
28247 				parent = body.parentNode;
28248 
28249 				parent.removeChild(body);
28250 				parent.appendChild(body);
28251 
28252 				body.focus();
28253 			}
28254 		},
28255 
28256 		_isHidden: function() {
28257 			var sel;
28258 
28259 			if (!isGecko) {
28260 				return 0;
28261 			}
28262 
28263 			// Weird, wheres that cursor selection?
28264 			sel = this.selection.getSel();
28265 			return (!sel || !sel.rangeCount || sel.rangeCount === 0);
28266 		}
28267 	};
28268 
28269 	extend(Editor.prototype, EditorObservable);
28270 
28271 	return Editor;
28272 });
28273 
28274 // Included from: js/tinymce/classes/util/I18n.js
28275 
28276 /**
28277  * I18n.js
28278  *
28279  * Copyright, Moxiecode Systems AB
28280  * Released under LGPL License.
28281  *
28282  * License: http://www.tinymce.com/license
28283  * Contributing: http://www.tinymce.com/contributing
28284  */
28285 
28286 /**
28287  * I18n class that handles translation of TinyMCE UI.
28288  * Uses po style with csharp style parameters.
28289  *
28290  * @class tinymce.util.I18n
28291  */
28292 define("tinymce/util/I18n", [], function() {
28293 	"use strict";
28294 
28295 	var data = {};
28296 
28297 	return {
28298 		/**
28299 		 * Property gets set to true if a RTL language pack was loaded.
28300 		 *
28301 		 * @property rtl
28302 		 * @type Boolean
28303 		 */
28304 		rtl: false,
28305 
28306 		/**
28307 		 * Adds translations for a specific language code.
28308 		 *
28309 		 * @method add
28310 		 * @param {String} code Language code like sv_SE.
28311 		 * @param {Array} items Name/value array with English en_US to sv_SE.
28312 		 */
28313 		add: function(code, items) {
28314 			for (var name in items) {
28315 				data[name] = items[name];
28316 			}
28317 
28318 			this.rtl = this.rtl || data._dir === 'rtl';
28319 		},
28320 
28321 		/**
28322 		 * Translates the specified text.
28323 		 *
28324 		 * It has a few formats:
28325 		 * I18n.translate("Text");
28326 		 * I18n.translate(["Text {0}/{1}", 0, 1]);
28327 		 * I18n.translate({raw: "Raw string"});
28328 		 *
28329 		 * @method translate
28330 		 * @param {String/Object/Array} text Text to translate.
28331 		 * @return {String} String that got translated.
28332 		 */
28333 		translate: function(text) {
28334 			if (typeof(text) == "undefined") {
28335 				return text;
28336 			}
28337 
28338 			if (typeof(text) != "string" && text.raw) {
28339 				return text.raw;
28340 			}
28341 
28342 			if (text.push) {
28343 				var values = text.slice(1);
28344 
28345 				text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
28346 					return values[match2];
28347 				});
28348 			}
28349 
28350 			return data[text] || text;
28351 		},
28352 
28353 		data: data
28354 	};
28355 });
28356 
28357 // Included from: js/tinymce/classes/FocusManager.js
28358 
28359 /**
28360  * FocusManager.js
28361  *
28362  * Copyright, Moxiecode Systems AB
28363  * Released under LGPL License.
28364  *
28365  * License: http://www.tinymce.com/license
28366  * Contributing: http://www.tinymce.com/contributing
28367  */
28368 
28369 /**
28370  * This class manages the focus/blur state of the editor. This class is needed since some
28371  * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
28372  *
28373  * This class will fire two events focus and blur on the editor instances that got affected.
28374  * It will also handle the restore of selection when the focus is lost and returned.
28375  *
28376  * @class tinymce.FocusManager
28377  */
28378 define("tinymce/FocusManager", [
28379 	"tinymce/dom/DOMUtils",
28380 	"tinymce/Env"
28381 ], function(DOMUtils, Env) {
28382 	var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
28383 
28384 	/**
28385 	 * Constructs a new focus manager instance.
28386 	 *
28387 	 * @constructor FocusManager
28388 	 * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
28389 	 */
28390 	function FocusManager(editorManager) {
28391 		function getActiveElement() {
28392 			try {
28393 				return document.activeElement;
28394 			} catch (ex) {
28395 				// IE sometimes fails to get the activeElement when resizing table
28396 				// TODO: Investigate this
28397 				return document.body;
28398 			}
28399 		}
28400 
28401 		// We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
28402 		// TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
28403 		function createBookmark(dom, rng) {
28404 			if (rng && rng.startContainer) {
28405 				// Verify that the range is within the root of the editor
28406 				if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
28407 					return;
28408 				}
28409 
28410 				return {
28411 					startContainer: rng.startContainer,
28412 					startOffset: rng.startOffset,
28413 					endContainer: rng.endContainer,
28414 					endOffset: rng.endOffset
28415 				};
28416 			}
28417 
28418 			return rng;
28419 		}
28420 
28421 		function bookmarkToRng(editor, bookmark) {
28422 			var rng;
28423 
28424 			if (bookmark.startContainer) {
28425 				rng = editor.getDoc().createRange();
28426 				rng.setStart(bookmark.startContainer, bookmark.startOffset);
28427 				rng.setEnd(bookmark.endContainer, bookmark.endOffset);
28428 			} else {
28429 				rng = bookmark;
28430 			}
28431 
28432 			return rng;
28433 		}
28434 
28435 		function isUIElement(elm) {
28436 			return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
28437 		}
28438 
28439 		function registerEvents(e) {
28440 			var editor = e.editor;
28441 
28442 			editor.on('init', function() {
28443 				// Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
28444 				if (editor.inline || Env.ie) {
28445 					// Use the onbeforedeactivate event when available since it works better see #7023
28446 					if ("onbeforedeactivate" in document && Env.ie < 9) {
28447 						editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
28448 							if (e.target != editor.getBody()) {
28449 								return;
28450 							}
28451 
28452 							try {
28453 								editor.lastRng = editor.selection.getRng();
28454 							} catch (ex) {
28455 								// IE throws "Unexcpected call to method or property access" some times so lets ignore it
28456 							}
28457 						});
28458 					} else {
28459 						// On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
28460 						editor.on('nodechange mouseup keyup', function(e) {
28461 							var node = getActiveElement();
28462 
28463 							// Only act on manual nodechanges
28464 							if (e.type == 'nodechange' && e.selectionChange) {
28465 								return;
28466 							}
28467 
28468 							// IE 11 reports active element as iframe not body of iframe
28469 							if (node && node.id == editor.id + '_ifr') {
28470 								node = editor.getBody();
28471 							}
28472 
28473 							if (editor.dom.isChildOf(node, editor.getBody())) {
28474 								editor.lastRng = editor.selection.getRng();
28475 							}
28476 						});
28477 					}
28478 
28479 					// Handles the issue with WebKit not retaining selection within inline document
28480 					// If the user releases the mouse out side the body since a mouse up event wont occur on the body
28481 					if (Env.webkit && !selectionChangeHandler) {
28482 						selectionChangeHandler = function() {
28483 							var activeEditor = editorManager.activeEditor;
28484 
28485 							if (activeEditor && activeEditor.selection) {
28486 								var rng = activeEditor.selection.getRng();
28487 
28488 								// Store when it's non collapsed
28489 								if (rng && !rng.collapsed) {
28490 									editor.lastRng = rng;
28491 								}
28492 							}
28493 						};
28494 
28495 						DOM.bind(document, 'selectionchange', selectionChangeHandler);
28496 					}
28497 				}
28498 			});
28499 
28500 			editor.on('setcontent', function() {
28501 				editor.lastRng = null;
28502 			});
28503 
28504 			// Remove last selection bookmark on mousedown see #6305
28505 			editor.on('mousedown', function() {
28506 				editor.selection.lastFocusBookmark = null;
28507 			});
28508 
28509 			editor.on('focusin', function() {
28510 				var focusedEditor = editorManager.focusedEditor;
28511 
28512 				if (editor.selection.lastFocusBookmark) {
28513 					editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
28514 					editor.selection.lastFocusBookmark = null;
28515 				}
28516 
28517 				if (focusedEditor != editor) {
28518 					if (focusedEditor) {
28519 						focusedEditor.fire('blur', {focusedEditor: editor});
28520 					}
28521 
28522 					editorManager.setActive(editor);
28523 					editorManager.focusedEditor = editor;
28524 					editor.fire('focus', {blurredEditor: focusedEditor});
28525 					editor.focus(true);
28526 				}
28527 
28528 				editor.lastRng = null;
28529 			});
28530 
28531 			editor.on('focusout', function() {
28532 				window.setTimeout(function() {
28533 					var focusedEditor = editorManager.focusedEditor;
28534 
28535 					// Still the same editor the the blur was outside any editor UI
28536 					if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
28537 						editor.fire('blur', {focusedEditor: null});
28538 						editorManager.focusedEditor = null;
28539 
28540 						// Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
28541 						if (editor.selection) {
28542 							editor.selection.lastFocusBookmark = null;
28543 						}
28544 					}
28545 				}, 0);
28546 			});
28547 
28548 			// Check if focus is moved to an element outside the active editor by checking if the target node
28549 			// isn't within the body of the activeEditor nor a UI element such as a dialog child control
28550 			if (!documentFocusInHandler) {
28551 				documentFocusInHandler = function(e) {
28552 					var activeEditor = editorManager.activeEditor;
28553 
28554 					if (activeEditor && e.target.ownerDocument == document) {
28555 						// Check to make sure we have a valid selection don't update the bookmark if it's
28556 						// a focusin to the body of the editor see #7025
28557 						if (activeEditor.selection && e.target != activeEditor.getBody()) {
28558 							activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
28559 						}
28560 
28561 						// Fire a blur event if the element isn't a UI element
28562 						if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
28563 							activeEditor.fire('blur', {focusedEditor: null});
28564 							editorManager.focusedEditor = null;
28565 						}
28566 					}
28567 				};
28568 
28569 				DOM.bind(document, 'focusin', documentFocusInHandler);
28570 			}
28571 
28572 			// Handle edge case when user starts the selection inside the editor and releases
28573 			// the mouse outside the editor producing a new selection. This weird workaround is needed since
28574 			// Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
28575 			if (editor.inline && !documentMouseUpHandler) {
28576 				documentMouseUpHandler = function(e) {
28577 					var activeEditor = editorManager.activeEditor;
28578 
28579 					if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
28580 						var rng = activeEditor.selection.getRng();
28581 
28582 						if (!rng.collapsed) {
28583 							activeEditor.lastRng = rng;
28584 						}
28585 					}
28586 				};
28587 
28588 				DOM.bind(document, 'mouseup', documentMouseUpHandler);
28589 			}
28590 		}
28591 
28592 		function unregisterDocumentEvents(e) {
28593 			if (editorManager.focusedEditor == e.editor) {
28594 				editorManager.focusedEditor = null;
28595 			}
28596 
28597 			if (!editorManager.activeEditor) {
28598 				DOM.unbind(document, 'selectionchange', selectionChangeHandler);
28599 				DOM.unbind(document, 'focusin', documentFocusInHandler);
28600 				DOM.unbind(document, 'mouseup', documentMouseUpHandler);
28601 				selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
28602 			}
28603 		}
28604 
28605 		editorManager.on('AddEditor', registerEvents);
28606 		editorManager.on('RemoveEditor', unregisterDocumentEvents);
28607 	}
28608 
28609 	/**
28610 	 * Returns true if the specified element is part of the UI for example an button or text input.
28611 	 *
28612 	 * @method isEditorUIElement
28613 	 * @param  {Element} elm Element to check if it's part of the UI or not.
28614 	 * @return {Boolean} True/false state if the element is part of the UI or not.
28615 	 */
28616 	FocusManager.isEditorUIElement = function(elm) {
28617 		// Needs to be converted to string since svg can have focus: #6776
28618 		return elm.className.toString().indexOf('mce-') !== -1;
28619 	};
28620 
28621 	return FocusManager;
28622 });
28623 
28624 // Included from: js/tinymce/classes/EditorManager.js
28625 
28626 /**
28627  * EditorManager.js
28628  *
28629  * Copyright, Moxiecode Systems AB
28630  * Released under LGPL License.
28631  *
28632  * License: http://www.tinymce.com/license
28633  * Contributing: http://www.tinymce.com/contributing
28634  */
28635 
28636 /**
28637  * This class used as a factory for manager for tinymce.Editor instances.
28638  *
28639  * @example
28640  * tinymce.EditorManager.init({});
28641  *
28642  * @class tinymce.EditorManager
28643  * @mixes tinymce.util.Observable
28644  * @static
28645  */
28646 define("tinymce/EditorManager", [
28647 	"tinymce/Editor",
28648 	"tinymce/dom/DomQuery",
28649 	"tinymce/dom/DOMUtils",
28650 	"tinymce/util/URI",
28651 	"tinymce/Env",
28652 	"tinymce/util/Tools",
28653 	"tinymce/util/Observable",
28654 	"tinymce/util/I18n",
28655 	"tinymce/FocusManager"
28656 ], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
28657 	var DOM = DOMUtils.DOM;
28658 	var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
28659 	var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
28660 
28661 	function removeEditorFromList(editor) {
28662 		var editors = EditorManager.editors, removedFromList;
28663 
28664 		delete editors[editor.id];
28665 
28666 		for (var i = 0; i < editors.length; i++) {
28667 			if (editors[i] == editor) {
28668 				editors.splice(i, 1);
28669 				removedFromList = true;
28670 				break;
28671 			}
28672 		}
28673 
28674 		// Select another editor since the active one was removed
28675 		if (EditorManager.activeEditor == editor) {
28676 			EditorManager.activeEditor = editors[0];
28677 		}
28678 
28679 		// Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
28680 		if (EditorManager.focusedEditor == editor) {
28681 			EditorManager.focusedEditor = null;
28682 		}
28683 
28684 		return removedFromList;
28685 	}
28686 
28687 	function purgeDestroyedEditor(editor) {
28688 		// User has manually destroyed the editor lets clean up the mess
28689 		if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
28690 			removeEditorFromList(editor);
28691 			editor.unbindAllNativeEvents();
28692 			editor.destroy(true);
28693 			editor = null;
28694 		}
28695 
28696 		return editor;
28697 	}
28698 
28699 	EditorManager = {
28700 		/**
28701 		 * Dom query instance.
28702 		 *
28703 		 * @property $
28704 		 * @type tinymce.dom.DomQuery
28705 		 */
28706 		$: DomQuery,
28707 
28708 		/**
28709 		 * Major version of TinyMCE build.
28710 		 *
28711 		 * @property majorVersion
28712 		 * @type String
28713 		 */
28714 		majorVersion: '4',
28715 
28716 		/**
28717 		 * Minor version of TinyMCE build.
28718 		 *
28719 		 * @property minorVersion
28720 		 * @type String
28721 		 */
28722 		minorVersion: '1.6',
28723 
28724 		/**
28725 		 * Release date of TinyMCE build.
28726 		 *
28727 		 * @property releaseDate
28728 		 * @type String
28729 		 */
28730 		releaseDate: '2014-10-08',
28731 
28732 		/**
28733 		 * Collection of editor instances.
28734 		 *
28735 		 * @property editors
28736 		 * @type Object
28737 		 * @example
28738 		 * for (edId in tinymce.editors)
28739 		 *     tinymce.editors[edId].save();
28740 		 */
28741 		editors: [],
28742 
28743 		/**
28744 		 * Collection of language pack data.
28745 		 *
28746 		 * @property i18n
28747 		 * @type Object
28748 		 */
28749 		i18n: I18n,
28750 
28751 		/**
28752 		 * Currently active editor instance.
28753 		 *
28754 		 * @property activeEditor
28755 		 * @type tinymce.Editor
28756 		 * @example
28757 		 * tinyMCE.activeEditor.selection.getContent();
28758 		 * tinymce.EditorManager.activeEditor.selection.getContent();
28759 		 */
28760 		activeEditor: null,
28761 
28762 		setup: function() {
28763 			var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
28764 
28765 			// Get base URL for the current document
28766 			documentBaseURL = document.location.href;
28767 
28768 			// Check if the URL is a document based format like: http://site/dir/file and file:///
28769 			// leave other formats like applewebdata://... intact
28770 			if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
28771 				documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
28772 
28773 				if (!/[\/\\]$/.test(documentBaseURL)) {
28774 					documentBaseURL += '/';
28775 				}
28776 			}
28777 
28778 			// If tinymce is defined and has a base use that or use the old tinyMCEPreInit
28779 			preInit = window.tinymce || window.tinyMCEPreInit;
28780 			if (preInit) {
28781 				baseURL = preInit.base || preInit.baseURL;
28782 				suffix = preInit.suffix;
28783 			} else {
28784 				// Get base where the tinymce script is located
28785 				var scripts = document.getElementsByTagName('script');
28786 				for (var i = 0; i < scripts.length; i++) {
28787 					src = scripts[i].src;
28788 
28789 					// Script types supported:
28790 					// tinymce.js tinymce.min.js tinymce.dev.js
28791 					// tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
28792 					// tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
28793 					if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
28794 						if (src.indexOf('.min') != -1) {
28795 							suffix = '.min';
28796 						}
28797 
28798 						baseURL = src.substring(0, src.lastIndexOf('/'));
28799 						break;
28800 					}
28801 				}
28802 
28803 				// We didn't find any baseURL by looking at the script elements
28804 				// Try to use the document.currentScript as a fallback
28805 				if (!baseURL && document.currentScript) {
28806 					src = document.currentScript.src;
28807 
28808 					if (src.indexOf('.min') != -1) {
28809 						suffix = '.min';
28810 					}
28811 
28812 					baseURL = src.substring(0, src.lastIndexOf('/'));
28813 				}
28814 			}
28815 
28816 			/**
28817 			 * Base URL where the root directory if TinyMCE is located.
28818 			 *
28819 			 * @property baseURL
28820 			 * @type String
28821 			 */
28822 			self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
28823 
28824 			/**
28825 			 * Document base URL where the current document is located.
28826 			 *
28827 			 * @property documentBaseURL
28828 			 * @type String
28829 			 */
28830 			self.documentBaseURL = documentBaseURL;
28831 
28832 			/**
28833 			 * Absolute baseURI for the installation path of TinyMCE.
28834 			 *
28835 			 * @property baseURI
28836 			 * @type tinymce.util.URI
28837 			 */
28838 			self.baseURI = new URI(self.baseURL);
28839 
28840 			/**
28841 			 * Current suffix to add to each plugin/theme that gets loaded for example ".min".
28842 			 *
28843 			 * @property suffix
28844 			 * @type String
28845 			 */
28846 			self.suffix = suffix;
28847 
28848 			self.focusManager = new FocusManager(self);
28849 		},
28850 
28851 		/**
28852 		 * Initializes a set of editors. This method will create editors based on various settings.
28853 		 *
28854 		 * @method init
28855 		 * @param {Object} settings Settings object to be passed to each editor instance.
28856 		 * @example
28857 		 * // Initializes a editor using the longer method
28858 		 * tinymce.EditorManager.init({
28859 		 *    some_settings : 'some value'
28860 		 * });
28861 		 *
28862 		 * // Initializes a editor instance using the shorter version
28863 		 * tinyMCE.init({
28864 		 *    some_settings : 'some value'
28865 		 * });
28866 		 */
28867 		init: function(settings) {
28868 			var self = this, editors = [];
28869 
28870 			function createId(elm) {
28871 				var id = elm.id;
28872 
28873 				// Use element id, or unique name or generate a unique id
28874 				if (!id) {
28875 					id = elm.name;
28876 
28877 					if (id && !DOM.get(id)) {
28878 						id = elm.name;
28879 					} else {
28880 						// Generate unique name
28881 						id = DOM.uniqueId();
28882 					}
28883 
28884 					elm.setAttribute('id', id);
28885 				}
28886 
28887 				return id;
28888 			}
28889 
28890 			function createEditor(id, settings, targetElm) {
28891 				if (!purgeDestroyedEditor(self.get(id))) {
28892 					var editor = new Editor(id, settings, self);
28893 
28894 					editor.targetElm = editor.targetElm || targetElm;
28895 					editors.push(editor);
28896 					editor.render();
28897 				}
28898 			}
28899 
28900 			function execCallback(name) {
28901 				var callback = settings[name];
28902 
28903 				if (!callback) {
28904 					return;
28905 				}
28906 
28907 				return callback.apply(self, Array.prototype.slice.call(arguments, 2));
28908 			}
28909 
28910 			function hasClass(elm, className) {
28911 				return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
28912 			}
28913 
28914 			function readyHandler() {
28915 				var l, co;
28916 
28917 				DOM.unbind(window, 'ready', readyHandler);
28918 
28919 				execCallback('onpageload');
28920 
28921 				if (settings.types) {
28922 					// Process type specific selector
28923 					each(settings.types, function(type) {
28924 						each(DOM.select(type.selector), function(elm) {
28925 							createEditor(createId(elm), extend({}, settings, type), elm);
28926 						});
28927 					});
28928 
28929 					return;
28930 				} else if (settings.selector) {
28931 					// Process global selector
28932 					each(DOM.select(settings.selector), function(elm) {
28933 						createEditor(createId(elm), settings, elm);
28934 					});
28935 
28936 					return;
28937 				} else if (settings.target) {
28938 					createEditor(createId(settings.target), settings);
28939 				}
28940 
28941 				// Fallback to old setting
28942 				switch (settings.mode) {
28943 					case "exact":
28944 						l = settings.elements || '';
28945 
28946 						if (l.length > 0) {
28947 							each(explode(l), function(id) {
28948 								var elm;
28949 
28950 								if ((elm = DOM.get(id))) {
28951 									createEditor(id, settings, elm);
28952 								} else {
28953 									each(document.forms, function(f) {
28954 										each(f.elements, function(e) {
28955 											if (e.name === id) {
28956 												id = 'mce_editor_' + instanceCounter++;
28957 												DOM.setAttrib(e, 'id', id);
28958 												createEditor(id, settings, e);
28959 											}
28960 										});
28961 									});
28962 								}
28963 							});
28964 						}
28965 						break;
28966 
28967 					case "textareas":
28968 					case "specific_textareas":
28969 						each(DOM.select('textarea'), function(elm) {
28970 							if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
28971 								return;
28972 							}
28973 
28974 							if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
28975 								createEditor(createId(elm), settings, elm);
28976 							}
28977 						});
28978 						break;
28979 				}
28980 
28981 				// Call onInit when all editors are initialized
28982 				if (settings.oninit) {
28983 					l = co = 0;
28984 
28985 					each(editors, function(ed) {
28986 						co++;
28987 
28988 						if (!ed.initialized) {
28989 							// Wait for it
28990 							ed.on('init', function() {
28991 								l++;
28992 
28993 								// All done
28994 								if (l == co) {
28995 									execCallback('oninit');
28996 								}
28997 							});
28998 						} else {
28999 							l++;
29000 						}
29001 
29002 						// All done
29003 						if (l == co) {
29004 							execCallback('oninit');
29005 						}
29006 					});
29007 				}
29008 			}
29009 
29010 			self.settings = settings;
29011 
29012 			DOM.bind(window, 'ready', readyHandler);
29013 		},
29014 
29015 		/**
29016 		 * Returns a editor instance by id.
29017 		 *
29018 		 * @method get
29019 		 * @param {String/Number} id Editor instance id or index to return.
29020 		 * @return {tinymce.Editor} Editor instance to return.
29021 		 * @example
29022 		 * // Adds an onclick event to an editor by id (shorter version)
29023 		 * tinymce.get('mytextbox').on('click', function(e) {
29024 		 *    ed.windowManager.alert('Hello world!');
29025 		 * });
29026 		 *
29027 		 * // Adds an onclick event to an editor by id (longer version)
29028 		 * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
29029 		 *    ed.windowManager.alert('Hello world!');
29030 		 * });
29031 		 */
29032 		get: function(id) {
29033 			if (!arguments.length) {
29034 				return this.editors;
29035 			}
29036 
29037 			return id in this.editors ? this.editors[id] : null;
29038 		},
29039 
29040 		/**
29041 		 * Adds an editor instance to the editor collection. This will also set it as the active editor.
29042 		 *
29043 		 * @method add
29044 		 * @param {tinymce.Editor} editor Editor instance to add to the collection.
29045 		 * @return {tinymce.Editor} The same instance that got passed in.
29046 		 */
29047 		add: function(editor) {
29048 			var self = this, editors = self.editors;
29049 
29050 			// Add named and index editor instance
29051 			editors[editor.id] = editor;
29052 			editors.push(editor);
29053 
29054 			// Doesn't call setActive method since we don't want
29055 			// to fire a bunch of activate/deactivate calls while initializing
29056 			self.activeEditor = editor;
29057 
29058 			/**
29059 			 * Fires when an editor is added to the EditorManager collection.
29060 			 *
29061 			 * @event AddEditor
29062 			 * @param {Object} e Event arguments.
29063 			 */
29064 			self.fire('AddEditor', {editor: editor});
29065 
29066 			if (!beforeUnloadDelegate) {
29067 				beforeUnloadDelegate = function() {
29068 					self.fire('BeforeUnload');
29069 				};
29070 
29071 				DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
29072 			}
29073 
29074 			return editor;
29075 		},
29076 
29077 		/**
29078 		 * Creates an editor instance and adds it to the EditorManager collection.
29079 		 *
29080 		 * @method createEditor
29081 		 * @param {String} id Instance id to use for editor.
29082 		 * @param {Object} settings Editor instance settings.
29083 		 * @return {tinymce.Editor} Editor instance that got created.
29084 		 */
29085 		createEditor: function(id, settings) {
29086 			return this.add(new Editor(id, settings, this));
29087 		},
29088 
29089 		/**
29090 		 * Removes a editor or editors form page.
29091 		 *
29092 		 * @example
29093 		 * // Remove all editors bound to divs
29094 		 * tinymce.remove('div');
29095 		 *
29096 		 * // Remove all editors bound to textareas
29097 		 * tinymce.remove('textarea');
29098 		 *
29099 		 * // Remove all editors
29100 		 * tinymce.remove();
29101 		 *
29102 		 * // Remove specific instance by id
29103 		 * tinymce.remove('#id');
29104 		 *
29105 		 * @method remove
29106 		 * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
29107 		 * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
29108 		 */
29109 		remove: function(selector) {
29110 			var self = this, i, editors = self.editors, editor;
29111 
29112 			// Remove all editors
29113 			if (!selector) {
29114 				for (i = editors.length - 1; i >= 0; i--) {
29115 					self.remove(editors[i]);
29116 				}
29117 
29118 				return;
29119 			}
29120 
29121 			// Remove editors by selector
29122 			if (typeof(selector) == "string") {
29123 				selector = selector.selector || selector;
29124 
29125 				each(DOM.select(selector), function(elm) {
29126 					editor = editors[elm.id];
29127 
29128 					if (editor) {
29129 						self.remove(editor);
29130 					}
29131 				});
29132 
29133 				return;
29134 			}
29135 
29136 			// Remove specific editor
29137 			editor = selector;
29138 
29139 			// Not in the collection
29140 			if (!editors[editor.id]) {
29141 				return null;
29142 			}
29143 
29144 			/**
29145 			 * Fires when an editor is removed from EditorManager collection.
29146 			 *
29147 			 * @event RemoveEditor
29148 			 * @param {Object} e Event arguments.
29149 			 */
29150 			if (removeEditorFromList(editor)) {
29151 				self.fire('RemoveEditor', {editor: editor});
29152 			}
29153 
29154 			if (!editors.length) {
29155 				DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
29156 			}
29157 
29158 			editor.remove();
29159 
29160 			return editor;
29161 		},
29162 
29163 		/**
29164 		 * Executes a specific command on the currently active editor.
29165 		 *
29166 		 * @method execCommand
29167 		 * @param {String} c Command to perform for example Bold.
29168 		 * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
29169 		 * @param {String} v Optional value parameter like for example an URL to a link.
29170 		 * @return {Boolean} true/false if the command was executed or not.
29171 		 */
29172 		execCommand: function(cmd, ui, value) {
29173 			var self = this, editor = self.get(value);
29174 
29175 			// Manager commands
29176 			switch (cmd) {
29177 				case "mceAddEditor":
29178 					if (!self.get(value)) {
29179 						new Editor(value, self.settings, self).render();
29180 					}
29181 
29182 					return true;
29183 
29184 				case "mceRemoveEditor":
29185 					if (editor) {
29186 						editor.remove();
29187 					}
29188 
29189 					return true;
29190 
29191 				case 'mceToggleEditor':
29192 					if (!editor) {
29193 						self.execCommand('mceAddEditor', 0, value);
29194 						return true;
29195 					}
29196 
29197 					if (editor.isHidden()) {
29198 						editor.show();
29199 					} else {
29200 						editor.hide();
29201 					}
29202 
29203 					return true;
29204 			}
29205 
29206 			// Run command on active editor
29207 			if (self.activeEditor) {
29208 				return self.activeEditor.execCommand(cmd, ui, value);
29209 			}
29210 
29211 			return false;
29212 		},
29213 
29214 		/**
29215 		 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
29216 		 *
29217 		 * @method triggerSave
29218 		 * @example
29219 		 * // Saves all contents
29220 		 * tinyMCE.triggerSave();
29221 		 */
29222 		triggerSave: function() {
29223 			each(this.editors, function(editor) {
29224 				editor.save();
29225 			});
29226 		},
29227 
29228 		/**
29229 		 * Adds a language pack, this gets called by the loaded language files like en.js.
29230 		 *
29231 		 * @method addI18n
29232 		 * @param {String} code Optional language code.
29233 		 * @param {Object} items Name/value object with translations.
29234 		 */
29235 		addI18n: function(code, items) {
29236 			I18n.add(code, items);
29237 		},
29238 
29239 		/**
29240 		 * Translates the specified string using the language pack items.
29241 		 *
29242 		 * @method translate
29243 		 * @param {String/Array/Object} text String to translate
29244 		 * @return {String} Translated string.
29245 		 */
29246 		translate: function(text) {
29247 			return I18n.translate(text);
29248 		},
29249 
29250 		/**
29251 		 * Sets the active editor instance and fires the deactivate/activate events.
29252 		 *
29253 		 * @method setActive
29254 		 * @param {tinymce.Editor} editor Editor instance to set as the active instance.
29255 		 */
29256 		setActive: function(editor) {
29257 			var activeEditor = this.activeEditor;
29258 
29259 			if (this.activeEditor != editor) {
29260 				if (activeEditor) {
29261 					activeEditor.fire('deactivate', {relatedTarget: editor});
29262 				}
29263 
29264 				editor.fire('activate', {relatedTarget: activeEditor});
29265 			}
29266 
29267 			this.activeEditor = editor;
29268 		}
29269 	};
29270 
29271 	extend(EditorManager, Observable);
29272 
29273 	EditorManager.setup();
29274 
29275 	// Export EditorManager as tinymce/tinymce in global namespace
29276 	window.tinymce = window.tinyMCE = EditorManager;
29277 
29278 	return EditorManager;
29279 });
29280 
29281 // Included from: js/tinymce/classes/LegacyInput.js
29282 
29283 /**
29284  * LegacyInput.js
29285  *
29286  * Copyright, Moxiecode Systems AB
29287  * Released under LGPL License.
29288  *
29289  * License: http://www.tinymce.com/license
29290  * Contributing: http://www.tinymce.com/contributing
29291  */
29292 
29293 define("tinymce/LegacyInput", [
29294 	"tinymce/EditorManager",
29295 	"tinymce/util/Tools"
29296 ], function(EditorManager, Tools) {
29297 	var each = Tools.each, explode = Tools.explode;
29298 
29299 	EditorManager.on('AddEditor', function(e) {
29300 		var editor = e.editor;
29301 
29302 		editor.on('preInit', function() {
29303 			var filters, fontSizes, dom, settings = editor.settings;
29304 
29305 			function replaceWithSpan(node, styles) {
29306 				each(styles, function(value, name) {
29307 					if (value) {
29308 						dom.setStyle(node, name, value);
29309 					}
29310 				});
29311 
29312 				dom.rename(node, 'span');
29313 			}
29314 
29315 			function convert(e) {
29316 				dom = editor.dom;
29317 
29318 				if (settings.convert_fonts_to_spans) {
29319 					each(dom.select('font,u,strike', e.node), function(node) {
29320 						filters[node.nodeName.toLowerCase()](dom, node);
29321 					});
29322 				}
29323 			}
29324 
29325 			if (settings.inline_styles) {
29326 				fontSizes = explode(settings.font_size_legacy_values);
29327 
29328 				filters = {
29329 					font: function(dom, node) {
29330 						replaceWithSpan(node, {
29331 							backgroundColor: node.style.backgroundColor,
29332 							color: node.color,
29333 							fontFamily: node.face,
29334 							fontSize: fontSizes[parseInt(node.size, 10) - 1]
29335 						});
29336 					},
29337 
29338 					u: function(dom, node) {
29339 						replaceWithSpan(node, {
29340 							textDecoration: 'underline'
29341 						});
29342 					},
29343 
29344 					strike: function(dom, node) {
29345 						replaceWithSpan(node, {
29346 							textDecoration: 'line-through'
29347 						});
29348 					}
29349 				};
29350 
29351 				editor.on('PreProcess SetContent', convert);
29352 			}
29353 		});
29354 	});
29355 });
29356 
29357 // Included from: js/tinymce/classes/util/XHR.js
29358 
29359 /**
29360  * XHR.js
29361  *
29362  * Copyright, Moxiecode Systems AB
29363  * Released under LGPL License.
29364  *
29365  * License: http://www.tinymce.com/license
29366  * Contributing: http://www.tinymce.com/contributing
29367  */
29368 
29369 /**
29370  * This class enables you to send XMLHTTPRequests cross browser.
29371  * @class tinymce.util.XHR
29372  * @mixes tinymce.util.Observable
29373  * @static
29374  * @example
29375  * // Sends a low level Ajax request
29376  * tinymce.util.XHR.send({
29377  *    url: 'someurl',
29378  *    success: function(text) {
29379  *       console.debug(text);
29380  *    }
29381  * });
29382  *
29383  * // Add custom header to XHR request
29384  * tinymce.util.XHR.on('beforeSend', function(e) {
29385  *     e.xhr.setRequestHeader('X-Requested-With', 'Something');
29386  * });
29387  */
29388 define("tinymce/util/XHR", [
29389 	"tinymce/util/Observable",
29390 	"tinymce/util/Tools"
29391 ], function(Observable, Tools) {
29392 	var XHR = {
29393 		/**
29394 		 * Sends a XMLHTTPRequest.
29395 		 * Consult the Wiki for details on what settings this method takes.
29396 		 *
29397 		 * @method send
29398 		 * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
29399 		 */
29400 		send: function(settings) {
29401 			var xhr, count = 0;
29402 
29403 			function ready() {
29404 				if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
29405 					if (settings.success && count < 10000 && xhr.status == 200) {
29406 						settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
29407 					} else if (settings.error) {
29408 						settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
29409 					}
29410 
29411 					xhr = null;
29412 				} else {
29413 					setTimeout(ready, 10);
29414 				}
29415 			}
29416 
29417 			// Default settings
29418 			settings.scope = settings.scope || this;
29419 			settings.success_scope = settings.success_scope || settings.scope;
29420 			settings.error_scope = settings.error_scope || settings.scope;
29421 			settings.async = settings.async === false ? false : true;
29422 			settings.data = settings.data || '';
29423 
29424 			xhr = new XMLHttpRequest();
29425 
29426 			if (xhr) {
29427 				if (xhr.overrideMimeType) {
29428 					xhr.overrideMimeType(settings.content_type);
29429 				}
29430 
29431 				xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
29432 
29433 				if (settings.crossDomain) {
29434 					xhr.withCredentials = true;
29435 				}
29436 
29437 				if (settings.content_type) {
29438 					xhr.setRequestHeader('Content-Type', settings.content_type);
29439 				}
29440 
29441 				xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
29442 
29443 				xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr;
29444 				xhr.send(settings.data);
29445 
29446 				// Syncronous request
29447 				if (!settings.async) {
29448 					return ready();
29449 				}
29450 
29451 				// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
29452 				setTimeout(ready, 10);
29453 			}
29454 		}
29455 	};
29456 
29457 	Tools.extend(XHR, Observable);
29458 
29459 	return XHR;
29460 });
29461 
29462 // Included from: js/tinymce/classes/util/JSON.js
29463 
29464 /**
29465  * JSON.js
29466  *
29467  * Copyright, Moxiecode Systems AB
29468  * Released under LGPL License.
29469  *
29470  * License: http://www.tinymce.com/license
29471  * Contributing: http://www.tinymce.com/contributing
29472  */
29473 
29474 /**
29475  * JSON parser and serializer class.
29476  *
29477  * @class tinymce.util.JSON
29478  * @static
29479  * @example
29480  * // JSON parse a string into an object
29481  * var obj = tinymce.util.JSON.parse(somestring);
29482  *
29483  * // JSON serialize a object into an string
29484  * var str = tinymce.util.JSON.serialize(obj);
29485  */
29486 define("tinymce/util/JSON", [], function() {
29487 	function serialize(o, quote) {
29488 		var i, v, t, name;
29489 
29490 		quote = quote || '"';
29491 
29492 		if (o === null) {
29493 			return 'null';
29494 		}
29495 
29496 		t = typeof o;
29497 
29498 		if (t == 'string') {
29499 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
29500 
29501 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
29502 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
29503 				if (quote === '"' && a === "'") {
29504 					return a;
29505 				}
29506 
29507 				i = v.indexOf(b);
29508 
29509 				if (i + 1) {
29510 					return '\\' + v.charAt(i + 1);
29511 				}
29512 
29513 				a = b.charCodeAt().toString(16);
29514 
29515 				return '\\u' + '0000'.substring(a.length) + a;
29516 			}) + quote;
29517 		}
29518 
29519 		if (t == 'object') {
29520 			if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
29521 				for (i = 0, v = '['; i < o.length; i++) {
29522 					v += (i > 0 ? ',' : '') + serialize(o[i], quote);
29523 				}
29524 
29525 				return v + ']';
29526 			}
29527 
29528 			v = '{';
29529 
29530 			for (name in o) {
29531 				if (o.hasOwnProperty(name)) {
29532 					v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
29533 						quote + ':' + serialize(o[name], quote) : '';
29534 				}
29535 			}
29536 
29537 			return v + '}';
29538 		}
29539 
29540 		return '' + o;
29541 	}
29542 
29543 	return {
29544 		/**
29545 		 * Serializes the specified object as a JSON string.
29546 		 *
29547 		 * @method serialize
29548 		 * @param {Object} obj Object to serialize as a JSON string.
29549 		 * @param {String} quote Optional quote string defaults to ".
29550 		 * @return {string} JSON string serialized from input.
29551 		 */
29552 		serialize: serialize,
29553 
29554 		/**
29555 		 * Unserializes/parses the specified JSON string into a object.
29556 		 *
29557 		 * @method parse
29558 		 * @param {string} s JSON String to parse into a JavaScript object.
29559 		 * @return {Object} Object from input JSON string or undefined if it failed.
29560 		 */
29561 		parse: function(text) {
29562 			try {
29563 				// Trick uglify JS
29564 				return window[String.fromCharCode(101) + 'val']('(' + text + ')');
29565 			} catch (ex) {
29566 				// Ignore
29567 			}
29568 		}
29569 
29570 		/**#@-*/
29571 	};
29572 });
29573 
29574 // Included from: js/tinymce/classes/util/JSONRequest.js
29575 
29576 /**
29577  * JSONRequest.js
29578  *
29579  * Copyright, Moxiecode Systems AB
29580  * Released under LGPL License.
29581  *
29582  * License: http://www.tinymce.com/license
29583  * Contributing: http://www.tinymce.com/contributing
29584  */
29585 
29586 /**
29587  * This class enables you to use JSON-RPC to call backend methods.
29588  *
29589  * @class tinymce.util.JSONRequest
29590  * @example
29591  * var json = new tinymce.util.JSONRequest({
29592  *     url: 'somebackend.php'
29593  * });
29594  *
29595  * // Send RPC call 1
29596  * json.send({
29597  *     method: 'someMethod1',
29598  *     params: ['a', 'b'],
29599  *     success: function(result) {
29600  *         console.dir(result);
29601  *     }
29602  * });
29603  *
29604  * // Send RPC call 2
29605  * json.send({
29606  *     method: 'someMethod2',
29607  *     params: ['a', 'b'],
29608  *     success: function(result) {
29609  *         console.dir(result);
29610  *     }
29611  * });
29612  */
29613 define("tinymce/util/JSONRequest", [
29614 	"tinymce/util/JSON",
29615 	"tinymce/util/XHR",
29616 	"tinymce/util/Tools"
29617 ], function(JSON, XHR, Tools) {
29618 	var extend = Tools.extend;
29619 
29620 	function JSONRequest(settings) {
29621 		this.settings = extend({}, settings);
29622 		this.count = 0;
29623 	}
29624 
29625 	/**
29626 	 * Simple helper function to send a JSON-RPC request without the need to initialize an object.
29627 	 * Consult the Wiki API documentation for more details on what you can pass to this function.
29628 	 *
29629 	 * @method sendRPC
29630 	 * @static
29631 	 * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
29632 	 */
29633 	JSONRequest.sendRPC = function(o) {
29634 		return new JSONRequest().send(o);
29635 	};
29636 
29637 	JSONRequest.prototype = {
29638 		/**
29639 		 * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
29640 		 *
29641 		 * @method send
29642 		 * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
29643 		 */
29644 		send: function(args) {
29645 			var ecb = args.error, scb = args.success;
29646 
29647 			args = extend(this.settings, args);
29648 
29649 			args.success = function(c, x) {
29650 				c = JSON.parse(c);
29651 
29652 				if (typeof(c) == 'undefined') {
29653 					c = {
29654 						error : 'JSON Parse error.'
29655 					};
29656 				}
29657 
29658 				if (c.error) {
29659 					ecb.call(args.error_scope || args.scope, c.error, x);
29660 				} else {
29661 					scb.call(args.success_scope || args.scope, c.result);
29662 				}
29663 			};
29664 
29665 			args.error = function(ty, x) {
29666 				if (ecb) {
29667 					ecb.call(args.error_scope || args.scope, ty, x);
29668 				}
29669 			};
29670 
29671 			args.data = JSON.serialize({
29672 				id: args.id || 'c' + (this.count++),
29673 				method: args.method,
29674 				params: args.params
29675 			});
29676 
29677 			// JSON content type for Ruby on rails. Bug: #1883287
29678 			args.content_type = 'application/json';
29679 
29680 			XHR.send(args);
29681 		}
29682 	};
29683 
29684 	return JSONRequest;
29685 });
29686 
29687 // Included from: js/tinymce/classes/util/JSONP.js
29688 
29689 /**
29690  * JSONP.js
29691  *
29692  * Copyright, Moxiecode Systems AB
29693  * Released under LGPL License.
29694  *
29695  * License: http://www.tinymce.com/license
29696  * Contributing: http://www.tinymce.com/contributing
29697  */
29698 
29699 define("tinymce/util/JSONP", [
29700 	"tinymce/dom/DOMUtils"
29701 ], function(DOMUtils) {
29702 	return {
29703 		callbacks: {},
29704 		count: 0,
29705 
29706 		send: function(settings) {
29707 			var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
29708 			var id = 'tinymce_jsonp_' + count;
29709 
29710 			self.callbacks[count] = function(json) {
29711 				dom.remove(id);
29712 				delete self.callbacks[count];
29713 
29714 				settings.callback(json);
29715 			};
29716 
29717 			dom.add(dom.doc.body, 'script', {
29718 				id: id,
29719 				src: settings.url,
29720 				type: 'text/javascript'
29721 			});
29722 
29723 			self.count++;
29724 		}
29725 	};
29726 });
29727 
29728 // Included from: js/tinymce/classes/util/LocalStorage.js
29729 
29730 /**
29731  * LocalStorage.js
29732  *
29733  * Copyright, Moxiecode Systems AB
29734  * Released under LGPL License.
29735  *
29736  * License: http://www.tinymce.com/license
29737  * Contributing: http://www.tinymce.com/contributing
29738  */
29739 
29740 /**
29741  * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
29742  * Storage is done using userData on IE 7 and a special serialization format. The format is designed
29743  * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
29744  * makes it possible to store for example HTML data.
29745  *
29746  * Storage format for userData:
29747  * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
29748  *
29749  * For example this data key1=value1,key2=value2 would be:
29750  * 4,key1,6,value1,4,key2,6,value2
29751  *
29752  * @class tinymce.util.LocalStorage
29753  * @static
29754  * @version 4.0
29755  * @example
29756  * tinymce.util.LocalStorage.setItem('key', 'value');
29757  * var value = tinymce.util.LocalStorage.getItem('key');
29758  */
29759 define("tinymce/util/LocalStorage", [], function() {
29760 	var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
29761 
29762 	// Check for native support
29763 	try {
29764 		if (window.localStorage) {
29765 			return localStorage;
29766 		}
29767 	} catch (ex) {
29768 		// Ignore
29769 	}
29770 
29771 	userDataKey = "tinymce";
29772 	storageElm = document.documentElement;
29773 	hasOldIEDataSupport = !!storageElm.addBehavior;
29774 
29775 	if (hasOldIEDataSupport) {
29776 		storageElm.addBehavior('#default#userData');
29777 	}
29778 
29779 	/**
29780 	 * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
29781 	 */
29782 	function updateKeys() {
29783 		keys = [];
29784 
29785 		for (var key in items) {
29786 			keys.push(key);
29787 		}
29788 
29789 		LocalStorage.length = keys.length;
29790 	}
29791 
29792 	/**
29793 	 * Loads the userData string and parses it into the items structure.
29794 	 */
29795 	function load() {
29796 		var key, data, value, pos = 0;
29797 
29798 		items = {};
29799 
29800 		// localStorage can be disabled on WebKit/Gecko so make a dummy storage
29801 		if (!hasOldIEDataSupport) {
29802 			return;
29803 		}
29804 
29805 		function next(end) {
29806 			var value, nextPos;
29807 
29808 			nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
29809 			if (nextPos === -1 || nextPos > data.length) {
29810 				return null;
29811 			}
29812 
29813 			value = data.substring(pos, nextPos);
29814 			pos = nextPos + 1;
29815 
29816 			return value;
29817 		}
29818 
29819 		storageElm.load(userDataKey);
29820 		data = storageElm.getAttribute(userDataKey) || '';
29821 
29822 		do {
29823 			var offset = next();
29824 			if (offset === null) {
29825 				break;
29826 			}
29827 
29828 			key = next(parseInt(offset, 32) || 0);
29829 			if (key !== null) {
29830 				offset = next();
29831 				if (offset === null) {
29832 					break;
29833 				}
29834 
29835 				value = next(parseInt(offset, 32) || 0);
29836 
29837 				if (key) {
29838 					items[key] = value;
29839 				}
29840 			}
29841 		} while (key !== null);
29842 
29843 		updateKeys();
29844 	}
29845 
29846 	/**
29847 	 * Saves the items structure into a the userData format.
29848 	 */
29849 	function save() {
29850 		var value, data = '';
29851 
29852 		// localStorage can be disabled on WebKit/Gecko so make a dummy storage
29853 		if (!hasOldIEDataSupport) {
29854 			return;
29855 		}
29856 
29857 		for (var key in items) {
29858 			value = items[key];
29859 			data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
29860 		}
29861 
29862 		storageElm.setAttribute(userDataKey, data);
29863 
29864 		try {
29865 			storageElm.save(userDataKey);
29866 		} catch (ex) {
29867 			// Ignore disk full
29868 		}
29869 
29870 		updateKeys();
29871 	}
29872 
29873 	LocalStorage = {
29874 		/**
29875 		 * Length of the number of items in storage.
29876 		 *
29877 		 * @property length
29878 		 * @type Number
29879 		 * @return {Number} Number of items in storage.
29880 		 */
29881 		//length:0,
29882 
29883 		/**
29884 		 * Returns the key name by index.
29885 		 *
29886 		 * @method key
29887 		 * @param {Number} index Index of key to return.
29888 		 * @return {String} Key value or null if it wasn't found.
29889 		 */
29890 		key: function(index) {
29891 			return keys[index];
29892 		},
29893 
29894 		/**
29895 		 * Returns the value if the specified key or null if it wasn't found.
29896 		 *
29897 		 * @method getItem
29898 		 * @param {String} key Key of item to retrive.
29899 		 * @return {String} Value of the specified item or null if it wasn't found.
29900 		 */
29901 		getItem: function(key) {
29902 			return key in items ? items[key] : null;
29903 		},
29904 
29905 		/**
29906 		 * Sets the value of the specified item by it's key.
29907 		 *
29908 		 * @method setItem
29909 		 * @param {String} key Key of the item to set.
29910 		 * @param {String} value Value of the item to set.
29911 		 */
29912 		setItem: function(key, value) {
29913 			items[key] = "" + value;
29914 			save();
29915 		},
29916 
29917 		/**
29918 		 * Removes the specified item by key.
29919 		 *
29920 		 * @method removeItem
29921 		 * @param {String} key Key of item to remove.
29922 		 */
29923 		removeItem: function(key) {
29924 			delete items[key];
29925 			save();
29926 		},
29927 
29928 		/**
29929 		 * Removes all items.
29930 		 *
29931 		 * @method clear
29932 		 */
29933 		clear: function() {
29934 			items = {};
29935 			save();
29936 		}
29937 	};
29938 
29939 	load();
29940 
29941 	return LocalStorage;
29942 });
29943 
29944 // Included from: js/tinymce/classes/Compat.js
29945 
29946 /**
29947  * Compat.js
29948  *
29949  * Copyright, Moxiecode Systems AB
29950  * Released under LGPL License.
29951  *
29952  * License: http://www.tinymce.com/license
29953  * Contributing: http://www.tinymce.com/contributing
29954  */
29955 
29956 /**
29957  * TinyMCE core class.
29958  *
29959  * @static
29960  * @class tinymce
29961  * @borrow-members tinymce.EditorManager
29962  * @borrow-members tinymce.util.Tools
29963  */
29964 define("tinymce/Compat", [
29965 	"tinymce/dom/DOMUtils",
29966 	"tinymce/dom/EventUtils",
29967 	"tinymce/dom/ScriptLoader",
29968 	"tinymce/AddOnManager",
29969 	"tinymce/util/Tools",
29970 	"tinymce/Env"
29971 ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
29972 	var tinymce = window.tinymce;
29973 
29974 	/**
29975 	 * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
29976 	 * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
29977 	 * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
29978 	 * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
29979 	 */
29980 	tinymce.DOM = DOMUtils.DOM;
29981 	tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
29982 	tinymce.PluginManager = AddOnManager.PluginManager;
29983 	tinymce.ThemeManager = AddOnManager.ThemeManager;
29984 
29985 	tinymce.dom = tinymce.dom || {};
29986 	tinymce.dom.Event = EventUtils.Event;
29987 
29988 	Tools.each(Tools, function(func, key) {
29989 		tinymce[key] = func;
29990 	});
29991 
29992 	Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
29993 		tinymce[name] = Env[name.substr(2).toLowerCase()];
29994 	});
29995 
29996 	return {};
29997 });
29998 
29999 // Describe the different namespaces
30000 
30001 /**
30002  * Root level namespace this contains classes directly releated to the TinyMCE editor.
30003  *
30004  * @namespace tinymce
30005  */
30006 
30007 /**
30008  * Contains classes for handling the browsers DOM.
30009  *
30010  * @namespace tinymce.dom
30011  */
30012 
30013 /**
30014  * Contains html parser and serializer logic.
30015  *
30016  * @namespace tinymce.html
30017  */
30018 
30019 /**
30020  * Contains the different UI types such as buttons, listboxes etc.
30021  *
30022  * @namespace tinymce.ui
30023  */
30024 
30025 /**
30026  * Contains various utility classes such as json parser, cookies etc.
30027  *
30028  * @namespace tinymce.util
30029  */
30030 
30031 // Included from: js/tinymce/classes/ui/Layout.js
30032 
30033 /**
30034  * Layout.js
30035  *
30036  * Copyright, Moxiecode Systems AB
30037  * Released under LGPL License.
30038  *
30039  * License: http://www.tinymce.com/license
30040  * Contributing: http://www.tinymce.com/contributing
30041  */
30042 
30043 /**
30044  * Base layout manager class.
30045  *
30046  * @class tinymce.ui.Layout
30047  */
30048 define("tinymce/ui/Layout", [
30049 	"tinymce/util/Class",
30050 	"tinymce/util/Tools"
30051 ], function(Class, Tools) {
30052 	"use strict";
30053 
30054 	return Class.extend({
30055 		Defaults: {
30056 			firstControlClass: 'first',
30057 			lastControlClass: 'last'
30058 		},
30059 
30060 		/**
30061 		 * Constructs a layout instance with the specified settings.
30062 		 *
30063 		 * @constructor
30064 		 * @param {Object} settings Name/value object with settings.
30065 		 */
30066 		init: function(settings) {
30067 			this.settings = Tools.extend({}, this.Defaults, settings);
30068 		},
30069 
30070 		/**
30071 		 * This method gets invoked before the layout renders the controls.
30072 		 *
30073 		 * @method preRender
30074 		 * @param {tinymce.ui.Container} container Container instance to preRender.
30075 		 */
30076 		preRender: function(container) {
30077 			container.addClass(this.settings.containerClass, 'body');
30078 		},
30079 
30080 		/**
30081 		 * Applies layout classes to the container.
30082 		 *
30083 		 * @private
30084 		 */
30085 		applyClasses: function(container) {
30086 			var self = this, settings = self.settings, items, firstClass, lastClass;
30087 
30088 			items = container.items().filter(':visible');
30089 			firstClass = settings.firstControlClass;
30090 			lastClass = settings.lastControlClass;
30091 
30092 			items.each(function(item) {
30093 				item.removeClass(firstClass).removeClass(lastClass);
30094 
30095 				if (settings.controlClass) {
30096 					item.addClass(settings.controlClass);
30097 				}
30098 			});
30099 
30100 			items.eq(0).addClass(firstClass);
30101 			items.eq(-1).addClass(lastClass);
30102 		},
30103 
30104 		/**
30105 		 * Renders the specified container and any layout specific HTML.
30106 		 *
30107 		 * @method renderHtml
30108 		 * @param {tinymce.ui.Container} container Container to render HTML for.
30109 		 */
30110 		renderHtml: function(container) {
30111 			var self = this, settings = self.settings, items, html = '';
30112 
30113 			items = container.items();
30114 			items.eq(0).addClass(settings.firstControlClass);
30115 			items.eq(-1).addClass(settings.lastControlClass);
30116 
30117 			items.each(function(item) {
30118 				if (settings.controlClass) {
30119 					item.addClass(settings.controlClass);
30120 				}
30121 
30122 				html += item.renderHtml();
30123 			});
30124 
30125 			return html;
30126 		},
30127 
30128 		/**
30129 		 * Recalculates the positions of the controls in the specified container.
30130 		 *
30131 		 * @method recalc
30132 		 * @param {tinymce.ui.Container} container Container instance to recalc.
30133 		 */
30134 		recalc: function() {
30135 		},
30136 
30137 		/**
30138 		 * This method gets invoked after the layout renders the controls.
30139 		 *
30140 		 * @method postRender
30141 		 * @param {tinymce.ui.Container} container Container instance to postRender.
30142 		 */
30143 		postRender: function() {
30144 		}
30145 	});
30146 });
30147 
30148 // Included from: js/tinymce/classes/ui/AbsoluteLayout.js
30149 
30150 /**
30151  * AbsoluteLayout.js
30152  *
30153  * Copyright, Moxiecode Systems AB
30154  * Released under LGPL License.
30155  *
30156  * License: http://www.tinymce.com/license
30157  * Contributing: http://www.tinymce.com/contributing
30158  */
30159 
30160 /**
30161  * LayoutManager for absolute positioning. This layout manager is more of
30162  * a base class for other layouts but can be created and used directly.
30163  *
30164  * @-x-less AbsoluteLayout.less
30165  * @class tinymce.ui.AbsoluteLayout
30166  * @extends tinymce.ui.Layout
30167  */
30168 define("tinymce/ui/AbsoluteLayout", [
30169 	"tinymce/ui/Layout"
30170 ], function(Layout) {
30171 	"use strict";
30172 
30173 	return Layout.extend({
30174 		Defaults: {
30175 			containerClass: 'abs-layout',
30176 			controlClass: 'abs-layout-item'
30177 		},
30178 
30179 		/**
30180 		 * Recalculates the positions of the controls in the specified container.
30181 		 *
30182 		 * @method recalc
30183 		 * @param {tinymce.ui.Container} container Container instance to recalc.
30184 		 */
30185 		recalc: function(container) {
30186 			container.items().filter(':visible').each(function(ctrl) {
30187 				var settings = ctrl.settings;
30188 
30189 				ctrl.layoutRect({
30190 					x: settings.x,
30191 					y: settings.y,
30192 					w: settings.w,
30193 					h: settings.h
30194 				});
30195 
30196 				if (ctrl.recalc) {
30197 					ctrl.recalc();
30198 				}
30199 			});
30200 		},
30201 
30202 		/**
30203 		 * Renders the specified container and any layout specific HTML.
30204 		 *
30205 		 * @method renderHtml
30206 		 * @param {tinymce.ui.Container} container Container to render HTML for.
30207 		 */
30208 		renderHtml: function(container) {
30209 			return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
30210 		}
30211 	});
30212 });
30213 
30214 // Included from: js/tinymce/classes/ui/Tooltip.js
30215 
30216 /**
30217  * Tooltip.js
30218  *
30219  * Copyright, Moxiecode Systems AB
30220  * Released under LGPL License.
30221  *
30222  * License: http://www.tinymce.com/license
30223  * Contributing: http://www.tinymce.com/contributing
30224  */
30225 
30226 /**
30227  * Creates a tooltip instance.
30228  *
30229  * @-x-less ToolTip.less
30230  * @class tinymce.ui.ToolTip
30231  * @extends tinymce.ui.Control
30232  * @mixes tinymce.ui.Movable
30233  */
30234 define("tinymce/ui/Tooltip", [
30235 	"tinymce/ui/Control",
30236 	"tinymce/ui/Movable"
30237 ], function(Control, Movable) {
30238 	return Control.extend({
30239 		Mixins: [Movable],
30240 
30241 		Defaults: {
30242 			classes: 'widget tooltip tooltip-n'
30243 		},
30244 
30245 		/**
30246 		 * Sets/gets the current label text.
30247 		 *
30248 		 * @method text
30249 		 * @param {String} [text] New label text.
30250 		 * @return {String|tinymce.ui.Tooltip} Current text or current label instance.
30251 		 */
30252 		text: function(value) {
30253 			var self = this;
30254 
30255 			if (typeof(value) != "undefined") {
30256 				self._value = value;
30257 
30258 				if (self._rendered) {
30259 					self.getEl().lastChild.innerHTML = self.encode(value);
30260 				}
30261 
30262 				return self;
30263 			}
30264 
30265 			return self._value;
30266 		},
30267 
30268 		/**
30269 		 * Renders the control as a HTML string.
30270 		 *
30271 		 * @method renderHtml
30272 		 * @return {String} HTML representing the control.
30273 		 */
30274 		renderHtml: function() {
30275 			var self = this, prefix = self.classPrefix;
30276 
30277 			return (
30278 				'<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
30279 					'<div class="' + prefix + 'tooltip-arrow"></div>' +
30280 					'<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
30281 				'</div>'
30282 			);
30283 		},
30284 
30285 		/**
30286 		 * Repaints the control after a layout operation.
30287 		 *
30288 		 * @method repaint
30289 		 */
30290 		repaint: function() {
30291 			var self = this, style, rect;
30292 
30293 			style = self.getEl().style;
30294 			rect = self._layoutRect;
30295 
30296 			style.left = rect.x + 'px';
30297 			style.top = rect.y + 'px';
30298 			style.zIndex = 0xFFFF + 0xFFFF;
30299 		}
30300 	});
30301 });
30302 
30303 // Included from: js/tinymce/classes/ui/Widget.js
30304 
30305 /**
30306  * Widget.js
30307  *
30308  * Copyright, Moxiecode Systems AB
30309  * Released under LGPL License.
30310  *
30311  * License: http://www.tinymce.com/license
30312  * Contributing: http://www.tinymce.com/contributing
30313  */
30314 
30315 /**
30316  * Widget base class a widget is a control that has a tooltip and some basic states.
30317  *
30318  * @class tinymce.ui.Widget
30319  * @extends tinymce.ui.Control
30320  */
30321 define("tinymce/ui/Widget", [
30322 	"tinymce/ui/Control",
30323 	"tinymce/ui/Tooltip"
30324 ], function(Control, Tooltip) {
30325 	"use strict";
30326 
30327 	var tooltip;
30328 
30329 	var Widget = Control.extend({
30330 		/**
30331 		 * Constructs a instance with the specified settings.
30332 		 *
30333 		 * @constructor
30334 		 * @param {Object} settings Name/value object with settings.
30335 		 * @setting {String} tooltip Tooltip text to display when hovering.
30336 		 * @setting {Boolean} autofocus True if the control should be focused when rendered.
30337 		 * @setting {String} text Text to display inside widget.
30338 		 */
30339 		init: function(settings) {
30340 			var self = this;
30341 
30342 			self._super(settings);
30343 			settings = self.settings;
30344 			self.canFocus = true;
30345 
30346 			if (settings.tooltip && Widget.tooltips !== false) {
30347 				self.on('mouseenter', function(e) {
30348 					var tooltip = self.tooltip().moveTo(-0xFFFF);
30349 
30350 					if (e.control == self) {
30351 						var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
30352 
30353 						tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
30354 						tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
30355 						tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');
30356 
30357 						tooltip.moveRel(self.getEl(), rel);
30358 					} else {
30359 						tooltip.hide();
30360 					}
30361 				});
30362 
30363 				self.on('mouseleave mousedown click', function() {
30364 					self.tooltip().hide();
30365 				});
30366 			}
30367 
30368 			self.aria('label', settings.ariaLabel || settings.tooltip);
30369 		},
30370 
30371 		/**
30372 		 * Returns the current tooltip instance.
30373 		 *
30374 		 * @method tooltip
30375 		 * @return {tinymce.ui.Tooltip} Tooltip instance.
30376 		 */
30377 		tooltip: function() {
30378 			if (!tooltip) {
30379 				tooltip = new Tooltip({type: 'tooltip'});
30380 				tooltip.renderTo();
30381 			}
30382 
30383 			return tooltip;
30384 		},
30385 
30386 		/**
30387 		 * Sets/gets the active state of the widget.
30388 		 *
30389 		 * @method active
30390 		 * @param {Boolean} [state] State if the control is active.
30391 		 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
30392 		 */
30393 		active: function(state) {
30394 			var self = this, undef;
30395 
30396 			if (state !== undef) {
30397 				self.aria('pressed', state);
30398 				self.toggleClass('active', state);
30399 			}
30400 
30401 			return self._super(state);
30402 		},
30403 
30404 		/**
30405 		 * Sets/gets the disabled state of the widget.
30406 		 *
30407 		 * @method disabled
30408 		 * @param {Boolean} [state] State if the control is disabled.
30409 		 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
30410 		 */
30411 		disabled: function(state) {
30412 			var self = this, undef;
30413 
30414 			if (state !== undef) {
30415 				self.aria('disabled', state);
30416 				self.toggleClass('disabled', state);
30417 			}
30418 
30419 			return self._super(state);
30420 		},
30421 
30422 		/**
30423 		 * Called after the control has been rendered.
30424 		 *
30425 		 * @method postRender
30426 		 */
30427 		postRender: function() {
30428 			var self = this, settings = self.settings;
30429 
30430 			self._rendered = true;
30431 
30432 			self._super();
30433 
30434 			if (!self.parent() && (settings.width || settings.height)) {
30435 				self.initLayoutRect();
30436 				self.repaint();
30437 			}
30438 
30439 			if (settings.autofocus) {
30440 				self.focus();
30441 			}
30442 		},
30443 
30444 		/**
30445 		 * Removes the current control from DOM and from UI collections.
30446 		 *
30447 		 * @method remove
30448 		 * @return {tinymce.ui.Control} Current control instance.
30449 		 */
30450 		remove: function() {
30451 			this._super();
30452 
30453 			if (tooltip) {
30454 				tooltip.remove();
30455 				tooltip = null;
30456 			}
30457 		}
30458 	});
30459 
30460 	return Widget;
30461 });
30462 
30463 // Included from: js/tinymce/classes/ui/Button.js
30464 
30465 /**
30466  * Button.js
30467  *
30468  * Copyright, Moxiecode Systems AB
30469  * Released under LGPL License.
30470  *
30471  * License: http://www.tinymce.com/license
30472  * Contributing: http://www.tinymce.com/contributing
30473  */
30474 
30475 /**
30476  * This class is used to create buttons. You can create them directly or through the Factory.
30477  *
30478  * @example
30479  * // Create and render a button to the body element
30480  * tinymce.ui.Factory.create({
30481  *     type: 'button',
30482  *     text: 'My button'
30483  * }).renderTo(document.body);
30484  *
30485  * @-x-less Button.less
30486  * @class tinymce.ui.Button
30487  * @extends tinymce.ui.Widget
30488  */
30489 define("tinymce/ui/Button", [
30490 	"tinymce/ui/Widget"
30491 ], function(Widget) {
30492 	"use strict";
30493 
30494 	return Widget.extend({
30495 		Defaults: {
30496 			classes: "widget btn",
30497 			role: "button"
30498 		},
30499 
30500 		/**
30501 		 * Constructs a new button instance with the specified settings.
30502 		 *
30503 		 * @constructor
30504 		 * @param {Object} settings Name/value object with settings.
30505 		 * @setting {String} size Size of the button small|medium|large.
30506 		 * @setting {String} image Image to use for icon.
30507 		 * @setting {String} icon Icon to use for button.
30508 		 */
30509 		init: function(settings) {
30510 			var self = this, size;
30511 
30512 			self.on('click mousedown', function(e) {
30513 				e.preventDefault();
30514 			});
30515 
30516 			self._super(settings);
30517 			size = settings.size;
30518 
30519 			if (settings.subtype) {
30520 				self.addClass(settings.subtype);
30521 			}
30522 
30523 			if (size) {
30524 				self.addClass('btn-' + size);
30525 			}
30526 		},
30527 
30528 		/**
30529 		 * Sets/gets the current button icon.
30530 		 *
30531 		 * @method icon
30532 		 * @param {String} [icon] New icon identifier.
30533 		 * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
30534 		 */
30535 		icon: function(icon) {
30536 			var self = this, prefix = self.classPrefix;
30537 
30538 			if (typeof(icon) == 'undefined') {
30539 				return self.settings.icon;
30540 			}
30541 
30542 			self.settings.icon = icon;
30543 			icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
30544 
30545 			if (self._rendered) {
30546 				var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
30547 
30548 				if (icon) {
30549 					if (!iconElm || iconElm != btnElm.firstChild) {
30550 						iconElm = document.createElement('i');
30551 						btnElm.insertBefore(iconElm, btnElm.firstChild);
30552 					}
30553 
30554 					iconElm.className = icon;
30555 				} else if (iconElm) {
30556 					btnElm.removeChild(iconElm);
30557 				}
30558 
30559 				self.text(self._text); // Set text again to fix whitespace between icon + text
30560 			}
30561 
30562 			return self;
30563 		},
30564 
30565 		/**
30566 		 * Repaints the button for example after it's been resizes by a layout engine.
30567 		 *
30568 		 * @method repaint
30569 		 */
30570 		repaint: function() {
30571 			var btnStyle = this.getEl().firstChild.style;
30572 
30573 			btnStyle.width = btnStyle.height = "100%";
30574 
30575 			this._super();
30576 		},
30577 
30578 		/**
30579 		 * Sets/gets the current button text.
30580 		 *
30581 		 * @method text
30582 		 * @param {String} [text] New button text.
30583 		 * @return {String|tinymce.ui.Button} Current text or current Button instance.
30584 		 */
30585 		text: function(text) {
30586 			var self = this;
30587 
30588 			if (self._rendered) {
30589 				var textNode = self.getEl().lastChild.lastChild;
30590 				if (textNode) {
30591 					textNode.data = self.translate(text);
30592 				}
30593 			}
30594 
30595 			return self._super(text);
30596 		},
30597 
30598 		/**
30599 		 * Renders the control as a HTML string.
30600 		 *
30601 		 * @method renderHtml
30602 		 * @return {String} HTML representing the control.
30603 		 */
30604 		renderHtml: function() {
30605 			var self = this, id = self._id, prefix = self.classPrefix;
30606 			var icon = self.settings.icon, image;
30607 
30608 			image = self.settings.image;
30609 			if (image) {
30610 				icon = 'none';
30611 
30612 				// Support for [high dpi, low dpi] image sources
30613 				if (typeof image != "string") {
30614 					image = window.getSelection ? image[0] : image[1];
30615 				}
30616 
30617 				image = ' style="background-image: url(\'' + image + '\')"';
30618 			} else {
30619 				image = '';
30620 			}
30621 
30622 			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
30623 
30624 			return (
30625 				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
30626 					'<button role="presentation" type="button" tabindex="-1">' +
30627 						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
30628 						(self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
30629 					'</button>' +
30630 				'</div>'
30631 			);
30632 		}
30633 	});
30634 });
30635 
30636 // Included from: js/tinymce/classes/ui/ButtonGroup.js
30637 
30638 /**
30639  * ButtonGroup.js
30640  *
30641  * Copyright, Moxiecode Systems AB
30642  * Released under LGPL License.
30643  *
30644  * License: http://www.tinymce.com/license
30645  * Contributing: http://www.tinymce.com/contributing
30646  */
30647 
30648 /**
30649  * This control enables you to put multiple buttons into a group. This is
30650  * useful when you want to combine similar toolbar buttons into a group.
30651  *
30652  * @example
30653  * // Create and render a buttongroup with two buttons to the body element
30654  * tinymce.ui.Factory.create({
30655  *     type: 'buttongroup',
30656  *     items: [
30657  *         {text: 'Button A'},
30658  *         {text: 'Button B'}
30659  *     ]
30660  * }).renderTo(document.body);
30661  *
30662  * @-x-less ButtonGroup.less
30663  * @class tinymce.ui.ButtonGroup
30664  * @extends tinymce.ui.Container
30665  */
30666 define("tinymce/ui/ButtonGroup", [
30667 	"tinymce/ui/Container"
30668 ], function(Container) {
30669 	"use strict";
30670 
30671 	return Container.extend({
30672 		Defaults: {
30673 			defaultType: 'button',
30674 			role: 'group'
30675 		},
30676 
30677 		/**
30678 		 * Renders the control as a HTML string.
30679 		 *
30680 		 * @method renderHtml
30681 		 * @return {String} HTML representing the control.
30682 		 */
30683 		renderHtml: function() {
30684 			var self = this, layout = self._layout;
30685 
30686 			self.addClass('btn-group');
30687 			self.preRender();
30688 			layout.preRender(self);
30689 
30690 			return (
30691 				'<div id="' + self._id + '" class="' + self.classes() + '">' +
30692 					'<div id="' + self._id + '-body">' +
30693 						(self.settings.html || '') + layout.renderHtml(self) +
30694 					'</div>' +
30695 				'</div>'
30696 			);
30697 		}
30698 	});
30699 });
30700 
30701 // Included from: js/tinymce/classes/ui/Checkbox.js
30702 
30703 /**
30704  * Checkbox.js
30705  *
30706  * Copyright, Moxiecode Systems AB
30707  * Released under LGPL License.
30708  *
30709  * License: http://www.tinymce.com/license
30710  * Contributing: http://www.tinymce.com/contributing
30711  */
30712 
30713 /**
30714  * This control creates a custom checkbox.
30715  *
30716  * @example
30717  * // Create and render a checkbox to the body element
30718  * tinymce.ui.Factory.create({
30719  *     type: 'checkbox',
30720  *     checked: true,
30721  *     text: 'My checkbox'
30722  * }).renderTo(document.body);
30723  *
30724  * @-x-less Checkbox.less
30725  * @class tinymce.ui.Checkbox
30726  * @extends tinymce.ui.Widget
30727  */
30728 define("tinymce/ui/Checkbox", [
30729 	"tinymce/ui/Widget"
30730 ], function(Widget) {
30731 	"use strict";
30732 
30733 	return Widget.extend({
30734 		Defaults: {
30735 			classes: "checkbox",
30736 			role: "checkbox",
30737 			checked: false
30738 		},
30739 
30740 		/**
30741 		 * Constructs a new Checkbox instance with the specified settings.
30742 		 *
30743 		 * @constructor
30744 		 * @param {Object} settings Name/value object with settings.
30745 		 * @setting {Boolean} checked True if the checkbox should be checked by default.
30746 		 */
30747 		init: function(settings) {
30748 			var self = this;
30749 
30750 			self._super(settings);
30751 
30752 			self.on('click mousedown', function(e) {
30753 				e.preventDefault();
30754 			});
30755 
30756 			self.on('click', function(e) {
30757 				e.preventDefault();
30758 
30759 				if (!self.disabled()) {
30760 					self.checked(!self.checked());
30761 				}
30762 			});
30763 
30764 			self.checked(self.settings.checked);
30765 		},
30766 
30767 		/**
30768 		 * Getter/setter function for the checked state.
30769 		 *
30770 		 * @method checked
30771 		 * @param {Boolean} [state] State to be set.
30772 		 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
30773 		 */
30774 		checked: function(state) {
30775 			var self = this;
30776 
30777 			if (typeof state != "undefined") {
30778 				if (state) {
30779 					self.addClass('checked');
30780 				} else {
30781 					self.removeClass('checked');
30782 				}
30783 
30784 				self._checked = state;
30785 				self.aria('checked', state);
30786 
30787 				return self;
30788 			}
30789 
30790 			return self._checked;
30791 		},
30792 
30793 		/**
30794 		 * Getter/setter function for the value state.
30795 		 *
30796 		 * @method value
30797 		 * @param {Boolean} [state] State to be set.
30798 		 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
30799 		 */
30800 		value: function(state) {
30801 			return this.checked(state);
30802 		},
30803 
30804 		/**
30805 		 * Renders the control as a HTML string.
30806 		 *
30807 		 * @method renderHtml
30808 		 * @return {String} HTML representing the control.
30809 		 */
30810 		renderHtml: function() {
30811 			var self = this, id = self._id, prefix = self.classPrefix;
30812 
30813 			return (
30814 				'<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
30815 					'<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
30816 					'<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
30817 				'</div>'
30818 			);
30819 		}
30820 	});
30821 });
30822 
30823 // Included from: js/tinymce/classes/ui/ComboBox.js
30824 
30825 /**
30826  * ComboBox.js
30827  *
30828  * Copyright, Moxiecode Systems AB
30829  * Released under LGPL License.
30830  *
30831  * License: http://www.tinymce.com/license
30832  * Contributing: http://www.tinymce.com/contributing
30833  */
30834 
30835 /**
30836  * This class creates a combobox control. Select box that you select a value from or
30837  * type a value into.
30838  *
30839  * @-x-less ComboBox.less
30840  * @class tinymce.ui.ComboBox
30841  * @extends tinymce.ui.Widget
30842  */
30843 define("tinymce/ui/ComboBox", [
30844 	"tinymce/ui/Widget",
30845 	"tinymce/ui/Factory",
30846 	"tinymce/ui/DomUtils"
30847 ], function(Widget, Factory, DomUtils) {
30848 	"use strict";
30849 
30850 	return Widget.extend({
30851 		/**
30852 		 * Constructs a new control instance with the specified settings.
30853 		 *
30854 		 * @constructor
30855 		 * @param {Object} settings Name/value object with settings.
30856 		 * @setting {String} placeholder Placeholder text to display.
30857 		 */
30858 		init: function(settings) {
30859 			var self = this;
30860 
30861 			self._super(settings);
30862 			self.addClass('combobox');
30863 			self.subinput = true;
30864 			self.ariaTarget = 'inp'; // TODO: Figure out a better way
30865 
30866 			settings = self.settings;
30867 			settings.menu = settings.menu || settings.values;
30868 
30869 			if (settings.menu) {
30870 				settings.icon = 'caret';
30871 			}
30872 
30873 			self.on('click', function(e) {
30874 				var elm = e.target, root = self.getEl();
30875 
30876 				while (elm && elm != root) {
30877 					if (elm.id && elm.id.indexOf('-open') != -1) {
30878 						self.fire('action');
30879 
30880 						if (settings.menu) {
30881 							self.showMenu();
30882 
30883 							if (e.aria) {
30884 								self.menu.items()[0].focus();
30885 							}
30886 						}
30887 					}
30888 
30889 					elm = elm.parentNode;
30890 				}
30891 			});
30892 
30893 			// TODO: Rework this
30894 			self.on('keydown', function(e) {
30895 				if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
30896 					self.parents().reverse().each(function(ctrl) {
30897 						e.preventDefault();
30898 						self.fire('change');
30899 
30900 						if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
30901 							ctrl.fire('submit', {data: ctrl.toJSON()});
30902 							return false;
30903 						}
30904 					});
30905 				}
30906 			});
30907 
30908 			if (settings.placeholder) {
30909 				self.addClass('placeholder');
30910 
30911 				self.on('focusin', function() {
30912 					if (!self._hasOnChange) {
30913 						DomUtils.on(self.getEl('inp'), 'change', function() {
30914 							self.fire('change');
30915 						});
30916 
30917 						self._hasOnChange = true;
30918 					}
30919 
30920 					if (self.hasClass('placeholder')) {
30921 						self.getEl('inp').value = '';
30922 						self.removeClass('placeholder');
30923 					}
30924 				});
30925 
30926 				self.on('focusout', function() {
30927 					if (self.value().length === 0) {
30928 						self.getEl('inp').value = settings.placeholder;
30929 						self.addClass('placeholder');
30930 					}
30931 				});
30932 			}
30933 		},
30934 
30935 		showMenu: function() {
30936 			var self = this, settings = self.settings, menu;
30937 
30938 			if (!self.menu) {
30939 				menu = settings.menu || [];
30940 
30941 				// Is menu array then auto constuct menu control
30942 				if (menu.length) {
30943 					menu = {
30944 						type: 'menu',
30945 						items: menu
30946 					};
30947 				} else {
30948 					menu.type = menu.type || 'menu';
30949 				}
30950 
30951 				self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
30952 				self.fire('createmenu');
30953 				self.menu.reflow();
30954 				self.menu.on('cancel', function(e) {
30955 					if (e.control === self.menu) {
30956 						self.focus();
30957 					}
30958 				});
30959 
30960 				self.menu.on('show hide', function(e) {
30961 					e.control.items().each(function(ctrl) {
30962 						ctrl.active(ctrl.value() == self.value());
30963 					});
30964 				}).fire('show');
30965 
30966 				self.menu.on('select', function(e) {
30967 					self.value(e.control.value());
30968 				});
30969 
30970 				self.on('focusin', function(e) {
30971 					if (e.target.tagName.toUpperCase() == 'INPUT') {
30972 						self.menu.hide();
30973 					}
30974 				});
30975 
30976 				self.aria('expanded', true);
30977 			}
30978 
30979 			self.menu.show();
30980 			self.menu.layoutRect({w: self.layoutRect().w});
30981 			self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
30982 		},
30983 
30984 		/**
30985 		 * Getter/setter function for the control value.
30986 		 *
30987 		 * @method value
30988 		 * @param {String} [value] Value to be set.
30989 		 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
30990 		 */
30991 		value: function(value) {
30992 			var self = this;
30993 
30994 			if (typeof(value) != "undefined") {
30995 				self._value = value;
30996 				self.removeClass('placeholder');
30997 
30998 				if (self._rendered) {
30999 					self.getEl('inp').value = value;
31000 				}
31001 
31002 				return self;
31003 			}
31004 
31005 			if (self._rendered) {
31006 				value = self.getEl('inp').value;
31007 
31008 				if (value != self.settings.placeholder) {
31009 					return value;
31010 				}
31011 
31012 				return '';
31013 			}
31014 
31015 			return self._value;
31016 		},
31017 
31018 		/**
31019 		 * Getter/setter function for the disabled state.
31020 		 *
31021 		 * @method value
31022 		 * @param {Boolean} [state] State to be set.
31023 		 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
31024 		 */
31025 		disabled: function(state) {
31026 			var self = this;
31027 
31028 			if (self._rendered && typeof(state) != 'undefined') {
31029 				self.getEl('inp').disabled = state;
31030 			}
31031 
31032 			return self._super(state);
31033 		},
31034 
31035 		/**
31036 		 * Focuses the input area of the control.
31037 		 *
31038 		 * @method focus
31039 		 */
31040 		focus: function() {
31041 			this.getEl('inp').focus();
31042 		},
31043 
31044 		/**
31045 		 * Repaints the control after a layout operation.
31046 		 *
31047 		 * @method repaint
31048 		 */
31049 		repaint: function() {
31050 			var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
31051 			var width, lineHeight;
31052 
31053 			if (openElm) {
31054 				width = rect.w - DomUtils.getSize(openElm).width - 10;
31055 			} else {
31056 				width = rect.w - 10;
31057 			}
31058 
31059 			// Detect old IE 7+8 add lineHeight to align caret vertically in the middle
31060 			var doc = document;
31061 			if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
31062 				lineHeight = (self.layoutRect().h - 2) + 'px';
31063 			}
31064 
31065 			DomUtils.css(elm.firstChild, {
31066 				width: width,
31067 				lineHeight: lineHeight
31068 			});
31069 
31070 			self._super();
31071 
31072 			return self;
31073 		},
31074 
31075 		/**
31076 		 * Post render method. Called after the control has been rendered to the target.
31077 		 *
31078 		 * @method postRender
31079 		 * @return {tinymce.ui.ComboBox} Current combobox instance.
31080 		 */
31081 		postRender: function() {
31082 			var self = this;
31083 
31084 			DomUtils.on(this.getEl('inp'), 'change', function() {
31085 				self.fire('change');
31086 			});
31087 
31088 			return self._super();
31089 		},
31090 
31091 		remove: function() {
31092 			DomUtils.off(this.getEl('inp'));
31093 			this._super();
31094 		},
31095 
31096 		/**
31097 		 * Renders the control as a HTML string.
31098 		 *
31099 		 * @method renderHtml
31100 		 * @return {String} HTML representing the control.
31101 		 */
31102 		renderHtml: function() {
31103 			var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
31104 			var value = settings.value || settings.placeholder || '';
31105 			var icon, text, openBtnHtml = '', extraAttrs = '';
31106 
31107 			if ("spellcheck" in settings) {
31108 				extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
31109 			}
31110 
31111 			if (settings.maxLength) {
31112 				extraAttrs += ' maxlength="' + settings.maxLength + '"';
31113 			}
31114 
31115 			if (settings.size) {
31116 				extraAttrs += ' size="' + settings.size + '"';
31117 			}
31118 
31119 			if (settings.subtype) {
31120 				extraAttrs += ' type="' + settings.subtype + '"';
31121 			}
31122 
31123 			if (self.disabled()) {
31124 				extraAttrs += ' disabled="disabled"';
31125 			}
31126 
31127 			icon = settings.icon;
31128 			if (icon && icon != 'caret') {
31129 				icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
31130 			}
31131 
31132 			text = self._text;
31133 
31134 			if (icon || text) {
31135 				openBtnHtml = (
31136 					'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
31137 						'<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
31138 							(icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
31139 							(text ? (icon ? ' ' : '') + text : '') +
31140 						'</button>' +
31141 					'</div>'
31142 				);
31143 
31144 				self.addClass('has-open');
31145 			}
31146 
31147 			return (
31148 				'<div id="' + id + '" class="' + self.classes() + '">' +
31149 					'<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
31150 					value + '" hidefocus="1"' + extraAttrs + ' />' +
31151 					openBtnHtml +
31152 				'</div>'
31153 			);
31154 		}
31155 	});
31156 });
31157 
31158 // Included from: js/tinymce/classes/ui/ColorBox.js
31159 
31160 /**
31161  * ColorBox.js
31162  *
31163  * Copyright, Moxiecode Systems AB
31164  * Released under LGPL License.
31165  *
31166  * License: http://www.tinymce.com/license
31167  * Contributing: http://www.tinymce.com/contributing
31168  */
31169 
31170 /**
31171  * This widget lets you enter colors and browse for colors by pressing the color button. It also displays
31172  * a preview of the current color.
31173  *
31174  * @-x-less ColorBox.less
31175  * @class tinymce.ui.ColorBox
31176  * @extends tinymce.ui.ComboBox
31177  */
31178 define("tinymce/ui/ColorBox", [
31179 	"tinymce/ui/ComboBox"
31180 ], function(ComboBox) {
31181 	"use strict";
31182 
31183 	return ComboBox.extend({
31184 		/**
31185 		 * Constructs a new control instance with the specified settings.
31186 		 *
31187 		 * @constructor
31188 		 * @param {Object} settings Name/value object with settings.
31189 		 */
31190 		init: function(settings) {
31191 			var self = this;
31192 
31193 			settings.spellcheck = false;
31194 
31195 			if (settings.onaction) {
31196 				settings.icon = 'none';
31197 			}
31198 
31199 			self._super(settings);
31200 
31201 			self.addClass('colorbox');
31202 			self.on('change keyup postrender', function() {
31203 				self.repaintColor(self.value());
31204 			});
31205 		},
31206 
31207 		repaintColor: function(value) {
31208 			var elm = this.getEl().getElementsByTagName('i')[0];
31209 
31210 			if (elm) {
31211 				try {
31212 					elm.style.background = value;
31213 				} catch (ex) {
31214 					// Ignore
31215 				}
31216 			}
31217 		},
31218 
31219 		value: function(value) {
31220 			var self = this;
31221 
31222 			if (typeof value != "undefined") {
31223 				if (self._rendered) {
31224 					self.repaintColor(value);
31225 				}
31226 			}
31227 
31228 			return self._super(value);
31229 		}
31230 	});
31231 });
31232 
31233 // Included from: js/tinymce/classes/ui/PanelButton.js
31234 
31235 /**
31236  * PanelButton.js
31237  *
31238  * Copyright, Moxiecode Systems AB
31239  * Released under LGPL License.
31240  *
31241  * License: http://www.tinymce.com/license
31242  * Contributing: http://www.tinymce.com/contributing
31243  */
31244 
31245 /**
31246  * Creates a new panel button.
31247  *
31248  * @class tinymce.ui.PanelButton
31249  * @extends tinymce.ui.Button
31250  */
31251 define("tinymce/ui/PanelButton", [
31252 	"tinymce/ui/Button",
31253 	"tinymce/ui/FloatPanel"
31254 ], function(Button, FloatPanel) {
31255 	"use strict";
31256 
31257 	return Button.extend({
31258 		/**
31259 		 * Shows the panel for the button.
31260 		 *
31261 		 * @method showPanel
31262 		 */
31263 		showPanel: function() {
31264 			var self = this, settings = self.settings;
31265 
31266 			self.active(true);
31267 
31268 			if (!self.panel) {
31269 				var panelSettings = settings.panel;
31270 
31271 				// Wrap panel in grid layout if type if specified
31272 				// This makes it possible to add forms or other containers directly in the panel option
31273 				if (panelSettings.type) {
31274 					panelSettings = {
31275 						layout: 'grid',
31276 						items: panelSettings
31277 					};
31278 				}
31279 
31280 				panelSettings.role = panelSettings.role || 'dialog';
31281 				panelSettings.popover = true;
31282 				panelSettings.autohide = true;
31283 				panelSettings.ariaRoot = true;
31284 
31285 				self.panel = new FloatPanel(panelSettings).on('hide', function() {
31286 					self.active(false);
31287 				}).on('cancel', function(e) {
31288 					e.stopPropagation();
31289 					self.focus();
31290 					self.hidePanel();
31291 				}).parent(self).renderTo(self.getContainerElm());
31292 
31293 				self.panel.fire('show');
31294 				self.panel.reflow();
31295 			} else {
31296 				self.panel.show();
31297 			}
31298 
31299 			self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
31300 		},
31301 
31302 		/**
31303 		 * Hides the panel for the button.
31304 		 *
31305 		 * @method hidePanel
31306 		 */
31307 		hidePanel: function() {
31308 			var self = this;
31309 
31310 			if (self.panel) {
31311 				self.panel.hide();
31312 			}
31313 		},
31314 
31315 		/**
31316 		 * Called after the control has been rendered.
31317 		 *
31318 		 * @method postRender
31319 		 */
31320 		postRender: function() {
31321 			var self = this;
31322 
31323 			self.aria('haspopup', true);
31324 
31325 			self.on('click', function(e) {
31326 				if (e.control === self) {
31327 					if (self.panel && self.panel.visible()) {
31328 						self.hidePanel();
31329 					} else {
31330 						self.showPanel();
31331 						self.panel.focus(!!e.aria);
31332 					}
31333 				}
31334 			});
31335 
31336 			return self._super();
31337 		},
31338 
31339 		remove: function() {
31340 			if (this.panel) {
31341 				this.panel.remove();
31342 				this.panel = null;
31343 			}
31344 
31345 			return this._super();
31346 		}
31347 	});
31348 });
31349 
31350 // Included from: js/tinymce/classes/ui/ColorButton.js
31351 
31352 /**
31353  * ColorButton.js
31354  *
31355  * Copyright, Moxiecode Systems AB
31356  * Released under LGPL License.
31357  *
31358  * License: http://www.tinymce.com/license
31359  * Contributing: http://www.tinymce.com/contributing
31360  */
31361 
31362 /**
31363  * This class creates a color button control. This is a split button in which the main
31364  * button has a visual representation of the currently selected color. When clicked
31365  * the caret button displays a color picker, allowing the user to select a new color.
31366  *
31367  * @-x-less ColorButton.less
31368  * @class tinymce.ui.ColorButton
31369  * @extends tinymce.ui.PanelButton
31370  */
31371 define("tinymce/ui/ColorButton", [
31372 	"tinymce/ui/PanelButton",
31373 	"tinymce/dom/DOMUtils"
31374 ], function(PanelButton, DomUtils) {
31375 	"use strict";
31376 
31377 	var DOM = DomUtils.DOM;
31378 
31379 	return PanelButton.extend({
31380 		/**
31381 		 * Constructs a new ColorButton instance with the specified settings.
31382 		 *
31383 		 * @constructor
31384 		 * @param {Object} settings Name/value object with settings.
31385 		 */
31386 		init: function(settings) {
31387 			this._super(settings);
31388 			this.addClass('colorbutton');
31389 		},
31390 
31391 		/**
31392 		 * Getter/setter for the current color.
31393 		 *
31394 		 * @method color
31395 		 * @param {String} [color] Color to set.
31396 		 * @return {String|tinymce.ui.ColorButton} Current color or current instance.
31397 		 */
31398 		color: function(color) {
31399 			if (color) {
31400 				this._color = color;
31401 				this.getEl('preview').style.backgroundColor = color;
31402 				return this;
31403 			}
31404 
31405 			return this._color;
31406 		},
31407 
31408 		/**
31409 		 * Renders the control as a HTML string.
31410 		 *
31411 		 * @method renderHtml
31412 		 * @return {String} HTML representing the control.
31413 		 */
31414 		renderHtml: function() {
31415 			var self = this, id = self._id, prefix = self.classPrefix;
31416 			var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
31417 			var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
31418 
31419 			return (
31420 				'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
31421 					'<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
31422 						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
31423 						'<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
31424 						(self._text ? (icon ? ' ' : '') + (self._text) : '') +
31425 					'</button>' +
31426 					'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
31427 						' <i class="' + prefix + 'caret"></i>' +
31428 					'</button>' +
31429 				'</div>'
31430 			);
31431 		},
31432 
31433 		/**
31434 		 * Called after the control has been rendered.
31435 		 *
31436 		 * @method postRender
31437 		 */
31438 		postRender: function() {
31439 			var self = this, onClickHandler = self.settings.onclick;
31440 
31441 			self.on('click', function(e) {
31442 				if (e.aria && e.aria.key == 'down') {
31443 					return;
31444 				}
31445 
31446 				if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
31447 					e.stopImmediatePropagation();
31448 					onClickHandler.call(self, e);
31449 				}
31450 			});
31451 
31452 			delete self.settings.onclick;
31453 
31454 			return self._super();
31455 		}
31456 	});
31457 });
31458 
31459 // Included from: js/tinymce/classes/util/Color.js
31460 
31461 /**
31462  * Color.js
31463  *
31464  * Copyright, Moxiecode Systems AB
31465  * Released under LGPL License.
31466  *
31467  * License: http://www.tinymce.com/license
31468  * Contributing: http://www.tinymce.com/contributing
31469  */
31470 
31471 /**
31472  * This class lets you parse/serialize colors and convert rgb/hsb.
31473  *
31474  * @class tinymce.util.Color
31475  * @example
31476  * var white = new tinymce.util.Color({r: 255, g: 255, b: 255});
31477  * var red = new tinymce.util.Color('#FF0000');
31478  *
31479  * console.log(white.toHex(), red.toHsv());
31480  */
31481 define("tinymce/util/Color", [], function() {
31482 	var min = Math.min, max = Math.max, round = Math.round;
31483 
31484 	/**
31485 	 * Constructs a new color instance.
31486 	 *
31487 	 * @constructor
31488 	 * @method Color
31489 	 * @param {String} value Optional initial value to parse.
31490 	 */
31491 	function Color(value) {
31492 		var self = this, r = 0, g = 0, b = 0;
31493 
31494 		function rgb2hsv(r, g, b) {
31495 			var h, s, v, d, minRGB, maxRGB;
31496 
31497 			h = 0;
31498 			s = 0;
31499 			v = 0;
31500 			r = r / 255;
31501 			g = g / 255;
31502 			b = b / 255;
31503 
31504 			minRGB = min(r, min(g, b));
31505 			maxRGB = max(r, max(g, b));
31506 
31507 			if (minRGB == maxRGB) {
31508 				v = minRGB;
31509 
31510 				return {
31511 					h: 0,
31512 					s: 0,
31513 					v: v * 100
31514 				};
31515 			}
31516 
31517 			/*eslint no-nested-ternary:0 */
31518 			d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
31519 			h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
31520 			h = 60 * (h - d / (maxRGB - minRGB));
31521 			s = (maxRGB - minRGB) / maxRGB;
31522 			v = maxRGB;
31523 
31524 			return {
31525 				h: round(h),
31526 				s: round(s * 100),
31527 				v: round(v * 100)
31528 			};
31529 		}
31530 
31531 		function hsvToRgb(hue, saturation, brightness) {
31532 			var side, chroma, x, match;
31533 
31534 			hue = (parseInt(hue, 10) || 0) % 360;
31535 			saturation = parseInt(saturation, 10) / 100;
31536 			brightness = parseInt(brightness, 10) / 100;
31537 			saturation = max(0, min(saturation, 1));
31538 			brightness = max(0, min(brightness, 1));
31539 
31540 			if (saturation === 0) {
31541 				r = g = b = round(255 * brightness);
31542 				return;
31543 			}
31544 
31545 			side = hue / 60;
31546 			chroma = brightness * saturation;
31547 			x = chroma * (1 - Math.abs(side % 2 - 1));
31548 			match = brightness - chroma;
31549 
31550 			switch (Math.floor(side)) {
31551 				case 0:
31552 					r = chroma;
31553 					g = x;
31554 					b = 0;
31555 					break;
31556 
31557 				case 1:
31558 					r = x;
31559 					g = chroma;
31560 					b = 0;
31561 					break;
31562 
31563 				case 2:
31564 					r = 0;
31565 					g = chroma;
31566 					b = x;
31567 					break;
31568 
31569 				case 3:
31570 					r = 0;
31571 					g = x;
31572 					b = chroma;
31573 					break;
31574 
31575 				case 4:
31576 					r = x;
31577 					g = 0;
31578 					b = chroma;
31579 					break;
31580 
31581 				case 5:
31582 					r = chroma;
31583 					g = 0;
31584 					b = x;
31585 					break;
31586 
31587 				default:
31588 					r = g = b = 0;
31589 			}
31590 
31591 			r = round(255 * (r + match));
31592 			g = round(255 * (g + match));
31593 			b = round(255 * (b + match));
31594 		}
31595 
31596 		/**
31597 		 * Returns the hex string of the current color. For example: #ff00ff
31598 		 *
31599 		 * @method toHex
31600 		 * @return {String} Hex string of current color.
31601 		 */
31602 		function toHex() {
31603 			function hex(val) {
31604 				val = parseInt(val, 10).toString(16);
31605 
31606 				return val.length > 1 ? val : '0' + val;
31607 			}
31608 
31609 			return '#' + hex(r) + hex(g) + hex(b);
31610 		}
31611 
31612 		/**
31613 		 * Returns the r, g, b values of the color. Each channel has a range from 0-255.
31614 		 *
31615 		 * @method toRgb
31616 		 * @return {Object} Object with r, g, b fields.
31617 		 */
31618 		function toRgb() {
31619 			return {
31620 				r: r,
31621 				g: g,
31622 				b: b
31623 			};
31624 		}
31625 
31626 		/**
31627 		 * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
31628 		 *
31629 		 * @method toHsv
31630 		 * @return {Object} Object with h, s, v fields.
31631 		 */
31632 		function toHsv() {
31633 			return rgb2hsv(r, g, b);
31634 		}
31635 
31636 		/**
31637 		 * Parses the specified value and populates the color instance.
31638 		 *
31639 		 * Supported format examples:
31640 		 *  * rbg(255,0,0)
31641 		 *  * #ff0000
31642 		 *  * #fff
31643 		 *  * {r: 255, g: 0, b: 0}
31644 		 *  * {h: 360, s: 100, v: 100}
31645 		 *
31646 		 * @method parse
31647 		 * @param {Object/String} value Color value to parse.
31648 		 * @return {tinymce.util.Color} Current color instance.
31649 		 */
31650 		function parse(value) {
31651 			var matches;
31652 
31653 			if (typeof value == 'object') {
31654 				if ("r" in value) {
31655 					r = value.r;
31656 					g = value.g;
31657 					b = value.b;
31658 				} else if ("v" in value) {
31659 					hsvToRgb(value.h, value.s, value.v);
31660 				}
31661 			} else {
31662 				if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
31663 					r = parseInt(matches[1], 10);
31664 					g = parseInt(matches[2], 10);
31665 					b = parseInt(matches[3], 10);
31666 				} else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
31667 					r = parseInt(matches[1], 16);
31668 					g = parseInt(matches[2], 16);
31669 					b = parseInt(matches[3], 16);
31670 				} else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
31671 					r = parseInt(matches[1] + matches[1], 16);
31672 					g = parseInt(matches[2] + matches[2], 16);
31673 					b = parseInt(matches[3] + matches[3], 16);
31674 				}
31675 			}
31676 
31677 			r = r < 0 ? 0 : (r > 255 ? 255 : r);
31678 			g = g < 0 ? 0 : (g > 255 ? 255 : g);
31679 			b = b < 0 ? 0 : (b > 255 ? 255 : b);
31680 
31681 			return self;
31682 		}
31683 
31684 		if (value) {
31685 			parse(value);
31686 		}
31687 
31688 		self.toRgb = toRgb;
31689 		self.toHsv = toHsv;
31690 		self.toHex = toHex;
31691 		self.parse = parse;
31692 	}
31693 
31694 	return Color;
31695 });
31696 
31697 // Included from: js/tinymce/classes/ui/ColorPicker.js
31698 
31699 /**
31700  * ColorPicker.js
31701  *
31702  * Copyright, Moxiecode Systems AB
31703  * Released under LGPL License.
31704  *
31705  * License: http://www.tinymce.com/license
31706  * Contributing: http://www.tinymce.com/contributing
31707  */
31708 
31709 /**
31710  * Color picker widget lets you select colors.
31711  *
31712  * @-x-less ColorPicker.less
31713  * @class tinymce.ui.ColorPicker
31714  * @extends tinymce.ui.Widget
31715  */
31716 define("tinymce/ui/ColorPicker", [
31717 	"tinymce/ui/Widget",
31718 	"tinymce/ui/DragHelper",
31719 	"tinymce/ui/DomUtils",
31720 	"tinymce/util/Color"
31721 ], function(Widget, DragHelper, DomUtils, Color) {
31722 	"use strict";
31723 
31724 	return Widget.extend({
31725 		Defaults: {
31726 			classes: "widget colorpicker"
31727 		},
31728 
31729 		/**
31730 		 * Constructs a new colorpicker instance with the specified settings.
31731 		 *
31732 		 * @constructor
31733 		 * @param {Object} settings Name/value object with settings.
31734 		 * @setting {String} color Initial color value.
31735 		 */
31736 		init: function(settings) {
31737 			this._super(settings);
31738 		},
31739 
31740 		postRender: function() {
31741 			var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;
31742 
31743 			hueRootElm = self.getEl('h');
31744 			huePointElm = self.getEl('hp');
31745 			svRootElm = self.getEl('sv');
31746 			svPointElm = self.getEl('svp');
31747 
31748 			function getPos(elm, event) {
31749 				var pos = DomUtils.getPos(elm), x, y;
31750 
31751 				x = event.pageX - pos.x;
31752 				y = event.pageY - pos.y;
31753 
31754 				x = Math.max(0, Math.min(x / elm.clientWidth, 1));
31755 				y = Math.max(0, Math.min(y / elm.clientHeight, 1));
31756 
31757 				return {
31758 					x: x,
31759 					y: y
31760 				};
31761 			}
31762 
31763 			function updateColor(hsv, hueUpdate) {
31764 				var hue = (360 - hsv.h) / 360;
31765 
31766 				DomUtils.css(huePointElm, {
31767 					top: (hue * 100) + '%'
31768 				});
31769 
31770 				if (!hueUpdate) {
31771 					DomUtils.css(svPointElm, {
31772 						left: hsv.s + '%',
31773 						top: (100 - hsv.v) + '%'
31774 					});
31775 				}
31776 
31777 				svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
31778 				self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
31779 			}
31780 
31781 			function updateSaturationAndValue(e) {
31782 				var pos;
31783 
31784 				pos = getPos(svRootElm, e);
31785 				hsv.s = pos.x * 100;
31786 				hsv.v = (1 - pos.y) * 100;
31787 
31788 				updateColor(hsv);
31789 				self.fire('change');
31790 			}
31791 
31792 			function updateHue(e) {
31793 				var pos;
31794 
31795 				pos = getPos(hueRootElm, e);
31796 				hsv = color.toHsv();
31797 				hsv.h = (1 - pos.y) * 360;
31798 				updateColor(hsv, true);
31799 				self.fire('change');
31800 			}
31801 
31802 			self._repaint = function() {
31803 				hsv = color.toHsv();
31804 				updateColor(hsv);
31805 			};
31806 
31807 			self._super();
31808 
31809 			self._svdraghelper = new DragHelper(self._id + '-sv', {
31810 				start: updateSaturationAndValue,
31811 				drag: updateSaturationAndValue
31812 			});
31813 
31814 			self._hdraghelper = new DragHelper(self._id + '-h', {
31815 				start: updateHue,
31816 				drag: updateHue
31817 			});
31818 
31819 			self._repaint();
31820 		},
31821 
31822 		rgb: function() {
31823 			return this.color().toRgb();
31824 		},
31825 
31826 		value: function(value) {
31827 			var self = this;
31828 
31829 			if (arguments.length) {
31830 				self.color().parse(value);
31831 
31832 				if (self._rendered) {
31833 					self._repaint();
31834 				}
31835 			} else {
31836 				return self.color().toHex();
31837 			}
31838 		},
31839 
31840 		color: function() {
31841 			if (!this._color) {
31842 				this._color = new Color();
31843 			}
31844 
31845 			return this._color;
31846 		},
31847 
31848 		/**
31849 		 * Renders the control as a HTML string.
31850 		 *
31851 		 * @method renderHtml
31852 		 * @return {String} HTML representing the control.
31853 		 */
31854 		renderHtml: function() {
31855 			var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
31856 			var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';
31857 
31858 			function getOldIeFallbackHtml() {
31859 				var i, l, html = '', gradientPrefix, stopsList;
31860 
31861 				gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
31862 				stopsList = stops.split(',');
31863 				for (i = 0, l = stopsList.length - 1; i < l; i++) {
31864 					html += (
31865 						'<div class="' + prefix + 'colorpicker-h-chunk" style="' +
31866 							'height:' + (100 / l) + '%;' +
31867 							gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
31868 							'-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
31869 						'"></div>'
31870 					);
31871 				}
31872 
31873 				return html;
31874 			}
31875 
31876 			var gradientCssText = (
31877 				'background: -ms-linear-gradient(top,' + stops + ');' +
31878 				'background: linear-gradient(to bottom,' + stops + ');'
31879 			);
31880 
31881 			hueHtml = (
31882 				'<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
31883 					getOldIeFallbackHtml() +
31884 					'<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
31885 				'</div>'
31886 			);
31887 
31888 			return (
31889 				'<div id="' + id + '" class="' + self.classes() + '">' +
31890 					'<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
31891 						'<div class="' + prefix + 'colorpicker-overlay1">' +
31892 							'<div class="' + prefix + 'colorpicker-overlay2">' +
31893 								'<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
31894 									'<div class="' + prefix + 'colorpicker-selector2"></div>' +
31895 								'</div>' +
31896 							'</div>' +
31897 						'</div>' +
31898 					'</div>' +
31899 					hueHtml +
31900 				'</div>'
31901 			);
31902 		}
31903 	});
31904 });
31905 
31906 // Included from: js/tinymce/classes/ui/Path.js
31907 
31908 /**
31909  * Path.js
31910  *
31911  * Copyright, Moxiecode Systems AB
31912  * Released under LGPL License.
31913  *
31914  * License: http://www.tinymce.com/license
31915  * Contributing: http://www.tinymce.com/contributing
31916  */
31917 
31918 /**
31919  * Creates a new path control.
31920  *
31921  * @-x-less Path.less
31922  * @class tinymce.ui.Path
31923  * @extends tinymce.ui.Widget
31924  */
31925 define("tinymce/ui/Path", [
31926 	"tinymce/ui/Widget"
31927 ], function(Widget) {
31928 	"use strict";
31929 
31930 	return Widget.extend({
31931 		/**
31932 		 * Constructs a instance with the specified settings.
31933 		 *
31934 		 * @constructor
31935 		 * @param {Object} settings Name/value object with settings.
31936 		 * @setting {String} delimiter Delimiter to display between items in path.
31937 		 */
31938 		init: function(settings) {
31939 			var self = this;
31940 
31941 			if (!settings.delimiter) {
31942 				settings.delimiter = '\u00BB';
31943 			}
31944 
31945 			self._super(settings);
31946 			self.addClass('path');
31947 			self.canFocus = true;
31948 
31949 			self.on('click', function(e) {
31950 				var index, target = e.target;
31951 
31952 				if ((index = target.getAttribute('data-index'))) {
31953 					self.fire('select', {value: self.data()[index], index: index});
31954 				}
31955 			});
31956 		},
31957 
31958 		/**
31959 		 * Focuses the current control.
31960 		 *
31961 		 * @method focus
31962 		 * @return {tinymce.ui.Control} Current control instance.
31963 		 */
31964 		focus: function() {
31965 			var self = this;
31966 
31967 			self.getEl().firstChild.focus();
31968 
31969 			return self;
31970 		},
31971 
31972 		/**
31973 		 * Sets/gets the data to be used for the path.
31974 		 *
31975 		 * @method data
31976 		 * @param {Array} data Array with items name is rendered to path.
31977 		 */
31978 		data: function(data) {
31979 			var self = this;
31980 
31981 			if (typeof(data) !== "undefined") {
31982 				self._data = data;
31983 				self.update();
31984 
31985 				return self;
31986 			}
31987 
31988 			return self._data;
31989 		},
31990 
31991 		/**
31992 		 * Updated the path.
31993 		 *
31994 		 * @private
31995 		 */
31996 		update: function() {
31997 			this.innerHtml(this._getPathHtml());
31998 		},
31999 
32000 		/**
32001 		 * Called after the control has been rendered.
32002 		 *
32003 		 * @method postRender
32004 		 */
32005 		postRender: function() {
32006 			var self = this;
32007 
32008 			self._super();
32009 
32010 			self.data(self.settings.data);
32011 		},
32012 
32013 		/**
32014 		 * Renders the control as a HTML string.
32015 		 *
32016 		 * @method renderHtml
32017 		 * @return {String} HTML representing the control.
32018 		 */
32019 		renderHtml: function() {
32020 			var self = this;
32021 
32022 			return (
32023 				'<div id="' + self._id + '" class="' + self.classes() + '">' +
32024 					self._getPathHtml() +
32025 				'</div>'
32026 			);
32027 		},
32028 
32029 		_getPathHtml: function() {
32030 			var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;
32031 
32032 			for (i = 0, l = parts.length; i < l; i++) {
32033 				html += (
32034 					(i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
32035 					'<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
32036 					i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
32037 				);
32038 			}
32039 
32040 			if (!html) {
32041 				html = '<div class="' + prefix + 'path-item">\u00a0</div>';
32042 			}
32043 
32044 			return html;
32045 		}
32046 	});
32047 });
32048 
32049 // Included from: js/tinymce/classes/ui/ElementPath.js
32050 
32051 /**
32052  * ElementPath.js
32053  *
32054  * Copyright, Moxiecode Systems AB
32055  * Released under LGPL License.
32056  *
32057  * License: http://www.tinymce.com/license
32058  * Contributing: http://www.tinymce.com/contributing
32059  */
32060 
32061 /**
32062  * This control creates an path for the current selections parent elements in TinyMCE.
32063  *
32064  * @class tinymce.ui.ElementPath
32065  * @extends tinymce.ui.Path
32066  */
32067 define("tinymce/ui/ElementPath", [
32068 	"tinymce/ui/Path",
32069 	"tinymce/EditorManager"
32070 ], function(Path, EditorManager) {
32071 	return Path.extend({
32072 		/**
32073 		 * Post render method. Called after the control has been rendered to the target.
32074 		 *
32075 		 * @method postRender
32076 		 * @return {tinymce.ui.ElementPath} Current combobox instance.
32077 		 */
32078 		postRender: function() {
32079 			var self = this, editor = EditorManager.activeEditor;
32080 
32081 			function isHidden(elm) {
32082 				if (elm.nodeType === 1) {
32083 					if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
32084 						return true;
32085 					}
32086 
32087 					if (elm.getAttribute('data-mce-type') === 'bookmark') {
32088 						return true;
32089 					}
32090 				}
32091 
32092 				return false;
32093 			}
32094 
32095 			self.on('select', function(e) {
32096 				editor.focus();
32097 				editor.selection.select(this.data()[e.index].element);
32098 				editor.nodeChanged();
32099 			});
32100 
32101 			editor.on('nodeChange', function(e) {
32102 				var outParents = [], parents = e.parents, i = parents.length;
32103 
32104 				while (i--) {
32105 					if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
32106 						var args = editor.fire('ResolveName', {
32107 							name: parents[i].nodeName.toLowerCase(),
32108 							target: parents[i]
32109 						});
32110 
32111 						if (!args.isDefaultPrevented()) {
32112 							outParents.push({name: args.name, element: parents[i]});
32113 						}
32114 
32115 						if (args.isPropagationStopped()) {
32116 							break;
32117 						}
32118 					}
32119 				}
32120 
32121 				self.data(outParents);
32122 			});
32123 
32124 			return self._super();
32125 		}
32126 	});
32127 });
32128 
32129 // Included from: js/tinymce/classes/ui/FormItem.js
32130 
32131 /**
32132  * FormItem.js
32133  *
32134  * Copyright, Moxiecode Systems AB
32135  * Released under LGPL License.
32136  *
32137  * License: http://www.tinymce.com/license
32138  * Contributing: http://www.tinymce.com/contributing
32139  */
32140 
32141 /**
32142  * This class is a container created by the form element with
32143  * a label and control item.
32144  *
32145  * @class tinymce.ui.FormItem
32146  * @extends tinymce.ui.Container
32147  * @setting {String} label Label to display for the form item.
32148  */
32149 define("tinymce/ui/FormItem", [
32150 	"tinymce/ui/Container"
32151 ], function(Container) {
32152 	"use strict";
32153 
32154 	return Container.extend({
32155 		Defaults: {
32156 			layout: 'flex',
32157 			align: 'center',
32158 			defaults: {
32159 				flex: 1
32160 			}
32161 		},
32162 
32163 		/**
32164 		 * Renders the control as a HTML string.
32165 		 *
32166 		 * @method renderHtml
32167 		 * @return {String} HTML representing the control.
32168 		 */
32169 		renderHtml: function() {
32170 			var self = this, layout = self._layout, prefix = self.classPrefix;
32171 
32172 			self.addClass('formitem');
32173 			layout.preRender(self);
32174 
32175 			return (
32176 				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
32177 					(self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
32178 						self.settings.title + '</div>') : '') +
32179 					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
32180 						(self.settings.html || '') + layout.renderHtml(self) +
32181 					'</div>' +
32182 				'</div>'
32183 			);
32184 		}
32185 	});
32186 });
32187 
32188 // Included from: js/tinymce/classes/ui/Form.js
32189 
32190 /**
32191  * Form.js
32192  *
32193  * Copyright, Moxiecode Systems AB
32194  * Released under LGPL License.
32195  *
32196  * License: http://www.tinymce.com/license
32197  * Contributing: http://www.tinymce.com/contributing
32198  */
32199 
32200 /**
32201  * This class creates a form container. A form container has the ability
32202  * to automatically wrap items in tinymce.ui.FormItem instances.
32203  *
32204  * Each FormItem instance is a container for the label and the item.
32205  *
32206  * @example
32207  * tinymce.ui.Factory.create({
32208  *     type: 'form',
32209  *     items: [
32210  *         {type: 'textbox', label: 'My text box'}
32211  *     ]
32212  * }).renderTo(document.body);
32213  *
32214  * @class tinymce.ui.Form
32215  * @extends tinymce.ui.Container
32216  */
32217 define("tinymce/ui/Form", [
32218 	"tinymce/ui/Container",
32219 	"tinymce/ui/FormItem",
32220 	"tinymce/util/Tools"
32221 ], function(Container, FormItem, Tools) {
32222 	"use strict";
32223 
32224 	return Container.extend({
32225 		Defaults: {
32226 			containerCls: 'form',
32227 			layout: 'flex',
32228 			direction: 'column',
32229 			align: 'stretch',
32230 			flex: 1,
32231 			padding: 20,
32232 			labelGap: 30,
32233 			spacing: 10,
32234 			callbacks: {
32235 				submit: function() {
32236 					this.submit();
32237 				}
32238 			}
32239 		},
32240 
32241 		/**
32242 		 * This method gets invoked before the control is rendered.
32243 		 *
32244 		 * @method preRender
32245 		 */
32246 		preRender: function() {
32247 			var self = this, items = self.items();
32248 
32249 			if (!self.settings.formItemDefaults) {
32250 				self.settings.formItemDefaults = {
32251 					layout: 'flex',
32252 					autoResize: "overflow",
32253 					defaults: {flex: 1}
32254 				};
32255 			}
32256 
32257 			// Wrap any labeled items in FormItems
32258 			items.each(function(ctrl) {
32259 				var formItem, inputId, label = ctrl.settings.label;
32260 
32261 				if (label) {
32262 					inputId = ctrl._id;
32263 
32264 					// point to the INPUTs of comboxes
32265 					// see the corresbonding TODO in ComboBox.js
32266 					if (ctrl.subinput) {
32267 						inputId += '-' + ctrl.ariaTarget;
32268 					}
32269 
32270 					formItem = new FormItem(Tools.extend({
32271 						items: {
32272 							type: 'label',
32273 							id: ctrl._id + '-l',
32274 							text: label,
32275 							flex: 0,
32276 							forId: inputId,
32277 							disabled: ctrl.disabled()
32278 						}
32279 					}, self.settings.formItemDefaults));
32280 
32281 					formItem.type = 'formitem';
32282 					ctrl.aria('labelledby', ctrl._id + '-l');
32283 
32284 					if (typeof(ctrl.settings.flex) == "undefined") {
32285 						ctrl.settings.flex = 1;
32286 					}
32287 
32288 					self.replace(ctrl, formItem);
32289 					formItem.add(ctrl);
32290 				}
32291 			});
32292 		},
32293 
32294 		/**
32295 		 * Recalcs label widths.
32296 		 *
32297 		 * @private
32298 		 */
32299 		recalcLabels: function() {
32300 			var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;
32301 
32302 			if (self.settings.labelGapCalc === false) {
32303 				return;
32304 			}
32305 
32306 			if (self.settings.labelGapCalc == "children") {
32307 				items = self.find('formitem');
32308 			} else {
32309 				items = self.items();
32310 			}
32311 
32312 			items.filter('formitem').each(function(item) {
32313 				var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
32314 
32315 				maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
32316 				labels.push(labelCtrl);
32317 			});
32318 
32319 			labelGap = self.settings.labelGap || 0;
32320 
32321 			i = labels.length;
32322 			while (i--) {
32323 				labels[i].settings.minWidth = maxLabelWidth + labelGap;
32324 			}
32325 		},
32326 
32327 		/**
32328 		 * Getter/setter for the visibility state.
32329 		 *
32330 		 * @method visible
32331 		 * @param {Boolean} [state] True/false state to show/hide.
32332 		 * @return {tinymce.ui.Form|Boolean} True/false state or current control.
32333 		 */
32334 		visible: function(state) {
32335 			var val = this._super(state);
32336 
32337 			if (state === true && this._rendered) {
32338 				this.recalcLabels();
32339 			}
32340 
32341 			return val;
32342 		},
32343 
32344 		/**
32345 		 * Fires a submit event with the serialized form.
32346 		 *
32347 		 * @method submit
32348 		 * @return {Object} Event arguments object.
32349 		 */
32350 		submit: function() {
32351 			return this.fire('submit', {data: this.toJSON()});
32352 		},
32353 
32354 		/**
32355 		 * Post render method. Called after the control has been rendered to the target.
32356 		 *
32357 		 * @method postRender
32358 		 * @return {tinymce.ui.ComboBox} Current combobox instance.
32359 		 */
32360 		postRender: function() {
32361 			var self = this;
32362 
32363 			self._super();
32364 			self.recalcLabels();
32365 			self.fromJSON(self.settings.data);
32366 		}
32367 	});
32368 });
32369 
32370 // Included from: js/tinymce/classes/ui/FieldSet.js
32371 
32372 /**
32373  * FieldSet.js
32374  *
32375  * Copyright, Moxiecode Systems AB
32376  * Released under LGPL License.
32377  *
32378  * License: http://www.tinymce.com/license
32379  * Contributing: http://www.tinymce.com/contributing
32380  */
32381 
32382 /**
32383  * This class creates fieldset containers.
32384  *
32385  * @-x-less FieldSet.less
32386  * @class tinymce.ui.FieldSet
32387  * @extends tinymce.ui.Form
32388  */
32389 define("tinymce/ui/FieldSet", [
32390 	"tinymce/ui/Form"
32391 ], function(Form) {
32392 	"use strict";
32393 
32394 	return Form.extend({
32395 		Defaults: {
32396 			containerCls: 'fieldset',
32397 			layout: 'flex',
32398 			direction: 'column',
32399 			align: 'stretch',
32400 			flex: 1,
32401 			padding: "25 15 5 15",
32402 			labelGap: 30,
32403 			spacing: 10,
32404 			border: 1
32405 		},
32406 
32407 		/**
32408 		 * Renders the control as a HTML string.
32409 		 *
32410 		 * @method renderHtml
32411 		 * @return {String} HTML representing the control.
32412 		 */
32413 		renderHtml: function() {
32414 			var self = this, layout = self._layout, prefix = self.classPrefix;
32415 
32416 			self.preRender();
32417 			layout.preRender(self);
32418 
32419 			return (
32420 				'<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
32421 					(self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
32422 						self.settings.title + '</legend>') : '') +
32423 					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
32424 						(self.settings.html || '') + layout.renderHtml(self) +
32425 					'</div>' +
32426 				'</fieldset>'
32427 			);
32428 		}
32429 	});
32430 });
32431 
32432 // Included from: js/tinymce/classes/ui/FilePicker.js
32433 
32434 /**
32435  * FilePicker.js
32436  *
32437  * Copyright, Moxiecode Systems AB
32438  * Released under LGPL License.
32439  *
32440  * License: http://www.tinymce.com/license
32441  * Contributing: http://www.tinymce.com/contributing
32442  */
32443 
32444 /*global tinymce:true */
32445 
32446 /**
32447  * This class creates a file picker control.
32448  *
32449  * @class tinymce.ui.FilePicker
32450  * @extends tinymce.ui.ComboBox
32451  */
32452 define("tinymce/ui/FilePicker", [
32453 	"tinymce/ui/ComboBox",
32454 	"tinymce/util/Tools"
32455 ], function(ComboBox, Tools) {
32456 	"use strict";
32457 
32458 	return ComboBox.extend({
32459 		/**
32460 		 * Constructs a new control instance with the specified settings.
32461 		 *
32462 		 * @constructor
32463 		 * @param {Object} settings Name/value object with settings.
32464 		 */
32465 		init: function(settings) {
32466 			var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings;
32467 			var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
32468 
32469 			settings.spellcheck = false;
32470 
32471 			fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
32472 			if (fileBrowserCallbackTypes) {
32473 				fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
32474 			}
32475 
32476 			if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
32477 				fileBrowserCallback = editorSettings.file_picker_callback;
32478 				if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
32479 					actionCallback = function() {
32480 						var meta = self.fire('beforecall').meta;
32481 
32482 						meta = Tools.extend({filetype: settings.filetype}, meta);
32483 
32484 						// file_picker_callback(callback, currentValue, metaData)
32485 						fileBrowserCallback.call(
32486 							editor,
32487 							function(value, meta) {
32488 								self.value(value).fire('change', {meta: meta});
32489 							},
32490 							self.value(),
32491 							meta
32492 						);
32493 					};
32494 				} else {
32495 					// Legacy callback: file_picker_callback(id, currentValue, filetype, window)
32496 					fileBrowserCallback = editorSettings.file_browser_callback;
32497 					if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
32498 						actionCallback = function() {
32499 							fileBrowserCallback(
32500 								self.getEl('inp').id,
32501 								self.value(),
32502 								settings.filetype,
32503 								window
32504 							);
32505 						};
32506 					}
32507 				}
32508 			}
32509 
32510 			if (actionCallback) {
32511 				settings.icon = 'browse';
32512 				settings.onaction = actionCallback;
32513 			}
32514 
32515 			self._super(settings);
32516 		}
32517 	});
32518 });
32519 
32520 // Included from: js/tinymce/classes/ui/FitLayout.js
32521 
32522 /**
32523  * FitLayout.js
32524  *
32525  * Copyright, Moxiecode Systems AB
32526  * Released under LGPL License.
32527  *
32528  * License: http://www.tinymce.com/license
32529  * Contributing: http://www.tinymce.com/contributing
32530  */
32531 
32532 /**
32533  * This layout manager will resize the control to be the size of it's parent container.
32534  * In other words width: 100% and height: 100%.
32535  *
32536  * @-x-less FitLayout.less
32537  * @class tinymce.ui.FitLayout
32538  * @extends tinymce.ui.AbsoluteLayout
32539  */
32540 define("tinymce/ui/FitLayout", [
32541 	"tinymce/ui/AbsoluteLayout"
32542 ], function(AbsoluteLayout) {
32543 	"use strict";
32544 
32545 	return AbsoluteLayout.extend({
32546 		/**
32547 		 * Recalculates the positions of the controls in the specified container.
32548 		 *
32549 		 * @method recalc
32550 		 * @param {tinymce.ui.Container} container Container instance to recalc.
32551 		 */
32552 		recalc: function(container) {
32553 			var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();
32554 
32555 			container.items().filter(':visible').each(function(ctrl) {
32556 				ctrl.layoutRect({
32557 					x: paddingBox.left,
32558 					y: paddingBox.top,
32559 					w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
32560 					h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
32561 				});
32562 
32563 				if (ctrl.recalc) {
32564 					ctrl.recalc();
32565 				}
32566 			});
32567 		}
32568 	});
32569 });
32570 
32571 // Included from: js/tinymce/classes/ui/FlexLayout.js
32572 
32573 /**
32574  * FlexLayout.js
32575  *
32576  * Copyright, Moxiecode Systems AB
32577  * Released under LGPL License.
32578  *
32579  * License: http://www.tinymce.com/license
32580  * Contributing: http://www.tinymce.com/contributing
32581  */
32582 
32583 /**
32584  * This layout manager works similar to the CSS flex box.
32585  *
32586  * @setting {String} direction row|row-reverse|column|column-reverse
32587  * @setting {Number} flex A positive-number to flex by.
32588  * @setting {String} align start|end|center|stretch
32589  * @setting {String} pack start|end|justify
32590  *
32591  * @class tinymce.ui.FlexLayout
32592  * @extends tinymce.ui.AbsoluteLayout
32593  */
32594 define("tinymce/ui/FlexLayout", [
32595 	"tinymce/ui/AbsoluteLayout"
32596 ], function(AbsoluteLayout) {
32597 	"use strict";
32598 
32599 	return AbsoluteLayout.extend({
32600 		/**
32601 		 * Recalculates the positions of the controls in the specified container.
32602 		 *
32603 		 * @method recalc
32604 		 * @param {tinymce.ui.Container} container Container instance to recalc.
32605 		 */
32606 		recalc: function(container) {
32607 			// A ton of variables, needs to be in the same scope for performance
32608 			var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
32609 			var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
32610 			var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
32611 			var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
32612 			var alignDeltaSizeName, alignContentSizeName;
32613 			var max = Math.max, min = Math.min;
32614 
32615 			// Get container items, properties and settings
32616 			items = container.items().filter(':visible');
32617 			contLayoutRect = container.layoutRect();
32618 			contPaddingBox = container._paddingBox;
32619 			contSettings = container.settings;
32620 			direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
32621 			align = contSettings.align;
32622 			pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
32623 			spacing = contSettings.spacing || 0;
32624 
32625 			if (direction == "row-reversed" || direction == "column-reverse") {
32626 				items = items.set(items.toArray().reverse());
32627 				direction = direction.split('-')[0];
32628 			}
32629 
32630 			// Setup axis variable name for row/column direction since the calculations is the same
32631 			if (direction == "column") {
32632 				posName = "y";
32633 				sizeName = "h";
32634 				minSizeName = "minH";
32635 				maxSizeName = "maxH";
32636 				innerSizeName = "innerH";
32637 				beforeName = 'top';
32638 				deltaSizeName = "deltaH";
32639 				contentSizeName = "contentH";
32640 
32641 				alignBeforeName = "left";
32642 				alignSizeName = "w";
32643 				alignAxisName = "x";
32644 				alignInnerSizeName = "innerW";
32645 				alignMinSizeName = "minW";
32646 				alignAfterName = "right";
32647 				alignDeltaSizeName = "deltaW";
32648 				alignContentSizeName = "contentW";
32649 			} else {
32650 				posName = "x";
32651 				sizeName = "w";
32652 				minSizeName = "minW";
32653 				maxSizeName = "maxW";
32654 				innerSizeName = "innerW";
32655 				beforeName = 'left';
32656 				deltaSizeName = "deltaW";
32657 				contentSizeName = "contentW";
32658 
32659 				alignBeforeName = "top";
32660 				alignSizeName = "h";
32661 				alignAxisName = "y";
32662 				alignInnerSizeName = "innerH";
32663 				alignMinSizeName = "minH";
32664 				alignAfterName = "bottom";
32665 				alignDeltaSizeName = "deltaH";
32666 				alignContentSizeName = "contentH";
32667 			}
32668 
32669 			// Figure out total flex, availableSpace and collect any max size elements
32670 			availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
32671 			maxAlignEndPos = totalFlex = 0;
32672 			for (i = 0, l = items.length; i < l; i++) {
32673 				ctrl = items[i];
32674 				ctrlLayoutRect = ctrl.layoutRect();
32675 				ctrlSettings = ctrl.settings;
32676 				flex = ctrlSettings.flex;
32677 				availableSpace -= (i < l - 1 ? spacing : 0);
32678 
32679 				if (flex > 0) {
32680 					totalFlex += flex;
32681 
32682 					// Flexed item has a max size then we need to check if we will hit that size
32683 					if (ctrlLayoutRect[maxSizeName]) {
32684 						maxSizeItems.push(ctrl);
32685 					}
32686 
32687 					ctrlLayoutRect.flex = flex;
32688 				}
32689 
32690 				availableSpace -= ctrlLayoutRect[minSizeName];
32691 
32692 				// Calculate the align end position to be used to check for overflow/underflow
32693 				size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
32694 				if (size > maxAlignEndPos) {
32695 					maxAlignEndPos = size;
32696 				}
32697 			}
32698 
32699 			// Calculate minW/minH
32700 			rect = {};
32701 			if (availableSpace < 0) {
32702 				rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
32703 			} else {
32704 				rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
32705 			}
32706 
32707 			rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
32708 
32709 			rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
32710 			rect[alignContentSizeName] = maxAlignEndPos;
32711 			rect.minW = min(rect.minW, contLayoutRect.maxW);
32712 			rect.minH = min(rect.minH, contLayoutRect.maxH);
32713 			rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
32714 			rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
32715 
32716 			// Resize container container if minSize was changed
32717 			if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
32718 				rect.w = rect.minW;
32719 				rect.h = rect.minH;
32720 
32721 				container.layoutRect(rect);
32722 				this.recalc(container);
32723 
32724 				// Forced recalc for example if items are hidden/shown
32725 				if (container._lastRect === null) {
32726 					var parentCtrl = container.parent();
32727 					if (parentCtrl) {
32728 						parentCtrl._lastRect = null;
32729 						parentCtrl.recalc();
32730 					}
32731 				}
32732 
32733 				return;
32734 			}
32735 
32736 			// Handle max size elements, check if they will become to wide with current options
32737 			ratio = availableSpace / totalFlex;
32738 			for (i = 0, l = maxSizeItems.length; i < l; i++) {
32739 				ctrl = maxSizeItems[i];
32740 				ctrlLayoutRect = ctrl.layoutRect();
32741 				maxSize = ctrlLayoutRect[maxSizeName];
32742 				size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
32743 
32744 				if (size > maxSize) {
32745 					availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
32746 					totalFlex -= ctrlLayoutRect.flex;
32747 					ctrlLayoutRect.flex = 0;
32748 					ctrlLayoutRect.maxFlexSize = maxSize;
32749 				} else {
32750 					ctrlLayoutRect.maxFlexSize = 0;
32751 				}
32752 			}
32753 
32754 			// Setup new ratio, target layout rect, start position
32755 			ratio = availableSpace / totalFlex;
32756 			pos = contPaddingBox[beforeName];
32757 			rect = {};
32758 
32759 			// Handle pack setting moves the start position to end, center
32760 			if (totalFlex === 0) {
32761 				if (pack == "end") {
32762 					pos = availableSpace + contPaddingBox[beforeName];
32763 				} else if (pack == "center") {
32764 					pos = Math.round(
32765 						(contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
32766 					) + contPaddingBox[beforeName];
32767 
32768 					if (pos < 0) {
32769 						pos = contPaddingBox[beforeName];
32770 					}
32771 				} else if (pack == "justify") {
32772 					pos = contPaddingBox[beforeName];
32773 					spacing = Math.floor(availableSpace / (items.length - 1));
32774 				}
32775 			}
32776 
32777 			// Default aligning (start) the other ones needs to be calculated while doing the layout
32778 			rect[alignAxisName] = contPaddingBox[alignBeforeName];
32779 
32780 			// Start laying out controls
32781 			for (i = 0, l = items.length; i < l; i++) {
32782 				ctrl = items[i];
32783 				ctrlLayoutRect = ctrl.layoutRect();
32784 				size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
32785 
32786 				// Align the control on the other axis
32787 				if (align === "center") {
32788 					rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
32789 				} else if (align === "stretch") {
32790 					rect[alignSizeName] = max(
32791 						ctrlLayoutRect[alignMinSizeName] || 0,
32792 						contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
32793 					);
32794 					rect[alignAxisName] = contPaddingBox[alignBeforeName];
32795 				} else if (align === "end") {
32796 					rect[alignAxisName] = contLayoutRect[alignInnerSizeName]  - ctrlLayoutRect[alignSizeName]  - contPaddingBox.top;
32797 				}
32798 
32799 				// Calculate new size based on flex
32800 				if (ctrlLayoutRect.flex > 0) {
32801 					size += ctrlLayoutRect.flex * ratio;
32802 				}
32803 
32804 				rect[sizeName] = size;
32805 				rect[posName] = pos;
32806 				ctrl.layoutRect(rect);
32807 
32808 				// Recalculate containers
32809 				if (ctrl.recalc) {
32810 					ctrl.recalc();
32811 				}
32812 
32813 				// Move x/y position
32814 				pos += size + spacing;
32815 			}
32816 		}
32817 	});
32818 });
32819 
32820 // Included from: js/tinymce/classes/ui/FlowLayout.js
32821 
32822 /**
32823  * FlowLayout.js
32824  *
32825  * Copyright, Moxiecode Systems AB
32826  * Released under LGPL License.
32827  *
32828  * License: http://www.tinymce.com/license
32829  * Contributing: http://www.tinymce.com/contributing
32830  */
32831 
32832 /**
32833  * This layout manager will place the controls by using the browsers native layout.
32834  *
32835  * @-x-less FlowLayout.less
32836  * @class tinymce.ui.FlowLayout
32837  * @extends tinymce.ui.Layout
32838  */
32839 define("tinymce/ui/FlowLayout", [
32840 	"tinymce/ui/Layout"
32841 ], function(Layout) {
32842 	return Layout.extend({
32843 		Defaults: {
32844 			containerClass: 'flow-layout',
32845 			controlClass: 'flow-layout-item',
32846 			endClass : 'break'
32847 		},
32848 
32849 		/**
32850 		 * Recalculates the positions of the controls in the specified container.
32851 		 *
32852 		 * @method recalc
32853 		 * @param {tinymce.ui.Container} container Container instance to recalc.
32854 		 */
32855 		recalc: function(container) {
32856 			container.items().filter(':visible').each(function(ctrl) {
32857 				if (ctrl.recalc) {
32858 					ctrl.recalc();
32859 				}
32860 			});
32861 		}
32862 	});
32863 });
32864 
32865 // Included from: js/tinymce/classes/ui/FormatControls.js
32866 
32867 /**
32868  * FormatControls.js
32869  *
32870  * Copyright, Moxiecode Systems AB
32871  * Released under LGPL License.
32872  *
32873  * License: http://www.tinymce.com/license
32874  * Contributing: http://www.tinymce.com/contributing
32875  */
32876 
32877 /**
32878  * Internal class containing all TinyMCE specific control types such as
32879  * format listboxes, fontlist boxes, toolbar buttons etc.
32880  *
32881  * @class tinymce.ui.FormatControls
32882  */
32883 define("tinymce/ui/FormatControls", [
32884 	"tinymce/ui/Control",
32885 	"tinymce/ui/Widget",
32886 	"tinymce/ui/FloatPanel",
32887 	"tinymce/util/Tools",
32888 	"tinymce/EditorManager",
32889 	"tinymce/Env"
32890 ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
32891 	var each = Tools.each;
32892 
32893 	EditorManager.on('AddEditor', function(e) {
32894 		if (e.editor.rtl) {
32895 			Control.rtl = true;
32896 		}
32897 
32898 		registerControls(e.editor);
32899 	});
32900 
32901 	Control.translate = function(text) {
32902 		return EditorManager.translate(text);
32903 	};
32904 
32905 	Widget.tooltips = !Env.iOS;
32906 
32907 	function registerControls(editor) {
32908 		var formatMenu;
32909 
32910 		function createListBoxChangeHandler(items, formatName) {
32911 			return function() {
32912 				var self = this;
32913 
32914 				editor.on('nodeChange', function(e) {
32915 					var formatter = editor.formatter;
32916 					var value = null;
32917 
32918 					each(e.parents, function(node) {
32919 						each(items, function(item) {
32920 							if (formatName) {
32921 								if (formatter.matchNode(node, formatName, {value: item.value})) {
32922 									value = item.value;
32923 								}
32924 							} else {
32925 								if (formatter.matchNode(node, item.value)) {
32926 									value = item.value;
32927 								}
32928 							}
32929 
32930 							if (value) {
32931 								return false;
32932 							}
32933 						});
32934 
32935 						if (value) {
32936 							return false;
32937 						}
32938 					});
32939 
32940 					self.value(value);
32941 				});
32942 			};
32943 		}
32944 
32945 		function createFormats(formats) {
32946 			formats = formats.replace(/;$/, '').split(';');
32947 
32948 			var i = formats.length;
32949 			while (i--) {
32950 				formats[i] = formats[i].split('=');
32951 			}
32952 
32953 			return formats;
32954 		}
32955 
32956 		function createFormatMenu() {
32957 			var count = 0, newFormats = [];
32958 
32959 			var defaultStyleFormats = [
32960 				{title: 'Headings', items: [
32961 					{title: 'Heading 1', format: 'h1'},
32962 					{title: 'Heading 2', format: 'h2'},
32963 					{title: 'Heading 3', format: 'h3'},
32964 					{title: 'Heading 4', format: 'h4'},
32965 					{title: 'Heading 5', format: 'h5'},
32966 					{title: 'Heading 6', format: 'h6'}
32967 				]},
32968 
32969 				{title: 'Inline', items: [
32970 					{title: 'Bold', icon: 'bold', format: 'bold'},
32971 					{title: 'Italic', icon: 'italic', format: 'italic'},
32972 					{title: 'Underline', icon: 'underline', format: 'underline'},
32973 					{title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
32974 					{title: 'Superscript', icon: 'superscript', format: 'superscript'},
32975 					{title: 'Subscript', icon: 'subscript', format: 'subscript'},
32976 					{title: 'Code', icon: 'code', format: 'code'}
32977 				]},
32978 
32979 				{title: 'Blocks', items: [
32980 					{title: 'Paragraph', format: 'p'},
32981 					{title: 'Blockquote', format: 'blockquote'},
32982 					{title: 'Div', format: 'div'},
32983 					{title: 'Pre', format: 'pre'}
32984 				]},
32985 
32986 				{title: 'Alignment', items: [
32987 					{title: 'Left', icon: 'alignleft', format: 'alignleft'},
32988 					{title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
32989 					{title: 'Right', icon: 'alignright', format: 'alignright'},
32990 					{title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
32991 				]}
32992 			];
32993 
32994 			function createMenu(formats) {
32995 				var menu = [];
32996 
32997 				if (!formats) {
32998 					return;
32999 				}
33000 
33001 				each(formats, function(format) {
33002 					var menuItem = {
33003 						text: format.title,
33004 						icon: format.icon
33005 					};
33006 
33007 					if (format.items) {
33008 						menuItem.menu = createMenu(format.items);
33009 					} else {
33010 						var formatName = format.format || "custom" + count++;
33011 
33012 						if (!format.format) {
33013 							format.name = formatName;
33014 							newFormats.push(format);
33015 						}
33016 
33017 						menuItem.format = formatName;
33018 						menuItem.cmd = format.cmd;
33019 					}
33020 
33021 					menu.push(menuItem);
33022 				});
33023 
33024 				return menu;
33025 			}
33026 
33027 			function createStylesMenu() {
33028 				var menu;
33029 
33030 				if (editor.settings.style_formats_merge) {
33031 					if (editor.settings.style_formats) {
33032 						menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
33033 					} else {
33034 						menu = createMenu(defaultStyleFormats);
33035 					}
33036 				} else {
33037 					menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
33038 				}
33039 
33040 				return menu;
33041 			}
33042 
33043 			editor.on('init', function() {
33044 				each(newFormats, function(format) {
33045 					editor.formatter.register(format.name, format);
33046 				});
33047 			});
33048 
33049 			return {
33050 				type: 'menu',
33051 				items: createStylesMenu(),
33052 				onPostRender: function(e) {
33053 					editor.fire('renderFormatsMenu', {control: e.control});
33054 				},
33055 				itemDefaults: {
33056 					preview: true,
33057 
33058 					textStyle: function() {
33059 						if (this.settings.format) {
33060 							return editor.formatter.getCssText(this.settings.format);
33061 						}
33062 					},
33063 
33064 					onPostRender: function() {
33065 						var self = this;
33066 
33067 						self.parent().on('show', function() {
33068 							var formatName, command;
33069 
33070 							formatName = self.settings.format;
33071 							if (formatName) {
33072 								self.disabled(!editor.formatter.canApply(formatName));
33073 								self.active(editor.formatter.match(formatName));
33074 							}
33075 
33076 							command = self.settings.cmd;
33077 							if (command) {
33078 								self.active(editor.queryCommandState(command));
33079 							}
33080 						});
33081 					},
33082 
33083 					onclick: function() {
33084 						if (this.settings.format) {
33085 							toggleFormat(this.settings.format);
33086 						}
33087 
33088 						if (this.settings.cmd) {
33089 							editor.execCommand(this.settings.cmd);
33090 						}
33091 					}
33092 				}
33093 			};
33094 		}
33095 
33096 		formatMenu = createFormatMenu();
33097 
33098 		// Simple format controls <control/format>:<UI text>
33099 		each({
33100 			bold: 'Bold',
33101 			italic: 'Italic',
33102 			underline: 'Underline',
33103 			strikethrough: 'Strikethrough',
33104 			subscript: 'Subscript',
33105 			superscript: 'Superscript'
33106 		}, function(text, name) {
33107 			editor.addButton(name, {
33108 				tooltip: text,
33109 				onPostRender: function() {
33110 					var self = this;
33111 
33112 					// TODO: Fix this
33113 					if (editor.formatter) {
33114 						editor.formatter.formatChanged(name, function(state) {
33115 							self.active(state);
33116 						});
33117 					} else {
33118 						editor.on('init', function() {
33119 							editor.formatter.formatChanged(name, function(state) {
33120 								self.active(state);
33121 							});
33122 						});
33123 					}
33124 				},
33125 				onclick: function() {
33126 					toggleFormat(name);
33127 				}
33128 			});
33129 		});
33130 
33131 		// Simple command controls <control>:[<UI text>,<Command>]
33132 		each({
33133 			outdent: ['Decrease indent', 'Outdent'],
33134 			indent: ['Increase indent', 'Indent'],
33135 			cut: ['Cut', 'Cut'],
33136 			copy: ['Copy', 'Copy'],
33137 			paste: ['Paste', 'Paste'],
33138 			help: ['Help', 'mceHelp'],
33139 			selectall: ['Select all', 'SelectAll'],
33140 			removeformat: ['Clear formatting', 'RemoveFormat'],
33141 			visualaid: ['Visual aids', 'mceToggleVisualAid'],
33142 			newdocument: ['New document', 'mceNewDocument']
33143 		}, function(item, name) {
33144 			editor.addButton(name, {
33145 				tooltip: item[0],
33146 				cmd: item[1]
33147 			});
33148 		});
33149 
33150 		// Simple command controls with format state
33151 		each({
33152 			blockquote: ['Blockquote', 'mceBlockQuote'],
33153 			numlist: ['Numbered list', 'InsertOrderedList'],
33154 			bullist: ['Bullet list', 'InsertUnorderedList'],
33155 			subscript: ['Subscript', 'Subscript'],
33156 			superscript: ['Superscript', 'Superscript'],
33157 			alignleft: ['Align left', 'JustifyLeft'],
33158 			aligncenter: ['Align center', 'JustifyCenter'],
33159 			alignright: ['Align right', 'JustifyRight'],
33160 			alignjustify: ['Justify', 'JustifyFull']
33161 		}, function(item, name) {
33162 			editor.addButton(name, {
33163 				tooltip: item[0],
33164 				cmd: item[1],
33165 				onPostRender: function() {
33166 					var self = this;
33167 
33168 					// TODO: Fix this
33169 					if (editor.formatter) {
33170 						editor.formatter.formatChanged(name, function(state) {
33171 							self.active(state);
33172 						});
33173 					} else {
33174 						editor.on('init', function() {
33175 							editor.formatter.formatChanged(name, function(state) {
33176 								self.active(state);
33177 							});
33178 						});
33179 					}
33180 				}
33181 			});
33182 		});
33183 
33184 		function toggleUndoRedoState(type) {
33185 			return function() {
33186 				var self = this;
33187 
33188 				type = type == 'redo' ? 'hasRedo' : 'hasUndo';
33189 
33190 				function checkState() {
33191 					return editor.undoManager ? editor.undoManager[type]() : false;
33192 				}
33193 
33194 				self.disabled(!checkState());
33195 				editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
33196 					self.disabled(!checkState());
33197 				});
33198 			};
33199 		}
33200 
33201 		function toggleVisualAidState() {
33202 			var self = this;
33203 
33204 			editor.on('VisualAid', function(e) {
33205 				self.active(e.hasVisual);
33206 			});
33207 
33208 			self.active(editor.hasVisual);
33209 		}
33210 
33211 		editor.addButton('undo', {
33212 			tooltip: 'Undo',
33213 			onPostRender: toggleUndoRedoState('undo'),
33214 			cmd: 'undo'
33215 		});
33216 
33217 		editor.addButton('redo', {
33218 			tooltip: 'Redo',
33219 			onPostRender: toggleUndoRedoState('redo'),
33220 			cmd: 'redo'
33221 		});
33222 
33223 		editor.addMenuItem('newdocument', {
33224 			text: 'New document',
33225 			shortcut: 'Ctrl+N',
33226 			icon: 'newdocument',
33227 			cmd: 'mceNewDocument'
33228 		});
33229 
33230 		editor.addMenuItem('undo', {
33231 			text: 'Undo',
33232 			icon: 'undo',
33233 			shortcut: 'Ctrl+Z',
33234 			onPostRender: toggleUndoRedoState('undo'),
33235 			cmd: 'undo'
33236 		});
33237 
33238 		editor.addMenuItem('redo', {
33239 			text: 'Redo',
33240 			icon: 'redo',
33241 			shortcut: 'Ctrl+Y',
33242 			onPostRender: toggleUndoRedoState('redo'),
33243 			cmd: 'redo'
33244 		});
33245 
33246 		editor.addMenuItem('visualaid', {
33247 			text: 'Visual aids',
33248 			selectable: true,
33249 			onPostRender: toggleVisualAidState,
33250 			cmd: 'mceToggleVisualAid'
33251 		});
33252 
33253 		each({
33254 			cut: ['Cut', 'Cut', 'Ctrl+X'],
33255 			copy: ['Copy', 'Copy', 'Ctrl+C'],
33256 			paste: ['Paste', 'Paste', 'Ctrl+V'],
33257 			selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
33258 			bold: ['Bold', 'Bold', 'Ctrl+B'],
33259 			italic: ['Italic', 'Italic', 'Ctrl+I'],
33260 			underline: ['Underline', 'Underline'],
33261 			strikethrough: ['Strikethrough', 'Strikethrough'],
33262 			subscript: ['Subscript', 'Subscript'],
33263 			superscript: ['Superscript', 'Superscript'],
33264 			removeformat: ['Clear formatting', 'RemoveFormat']
33265 		}, function(item, name) {
33266 			editor.addMenuItem(name, {
33267 				text: item[0],
33268 				icon: name,
33269 				shortcut: item[2],
33270 				cmd: item[1]
33271 			});
33272 		});
33273 
33274 		editor.on('mousedown', function() {
33275 			FloatPanel.hideAll();
33276 		});
33277 
33278 		function toggleFormat(fmt) {
33279 			if (fmt.control) {
33280 				fmt = fmt.control.value();
33281 			}
33282 
33283 			if (fmt) {
33284 				editor.execCommand('mceToggleFormat', false, fmt);
33285 			}
33286 		}
33287 
33288 		editor.addButton('styleselect', {
33289 			type: 'menubutton',
33290 			text: 'Formats',
33291 			menu: formatMenu
33292 		});
33293 
33294 		editor.addButton('formatselect', function() {
33295 			var items = [], blocks = createFormats(editor.settings.block_formats ||
33296 				'Paragraph=p;' +
33297 				'Address=address;' +
33298 				'Pre=pre;' +
33299 				'Heading 1=h1;' +
33300 				'Heading 2=h2;' +
33301 				'Heading 3=h3;' +
33302 				'Heading 4=h4;' +
33303 				'Heading 5=h5;' +
33304 				'Heading 6=h6'
33305 			);
33306 
33307 			each(blocks, function(block) {
33308 				items.push({
33309 					text: block[0],
33310 					value: block[1],
33311 					textStyle: function() {
33312 						return editor.formatter.getCssText(block[1]);
33313 					}
33314 				});
33315 			});
33316 
33317 			return {
33318 				type: 'listbox',
33319 				text: blocks[0][0],
33320 				values: items,
33321 				fixedWidth: true,
33322 				onselect: toggleFormat,
33323 				onPostRender: createListBoxChangeHandler(items)
33324 			};
33325 		});
33326 
33327 		editor.addButton('fontselect', function() {
33328 			var defaultFontsFormats =
33329 				'Andale Mono=andale mono,times;' +
33330 				'Arial=arial,helvetica,sans-serif;' +
33331 				'Arial Black=arial black,avant garde;' +
33332 				'Book Antiqua=book antiqua,palatino;' +
33333 				'Comic Sans MS=comic sans ms,sans-serif;' +
33334 				'Courier New=courier new,courier;' +
33335 				'Georgia=georgia,palatino;' +
33336 				'Helvetica=helvetica;' +
33337 				'Impact=impact,chicago;' +
33338 				'Symbol=symbol;' +
33339 				'Tahoma=tahoma,arial,helvetica,sans-serif;' +
33340 				'Terminal=terminal,monaco;' +
33341 				'Times New Roman=times new roman,times;' +
33342 				'Trebuchet MS=trebuchet ms,geneva;' +
33343 				'Verdana=verdana,geneva;' +
33344 				'Webdings=webdings;' +
33345 				'Wingdings=wingdings,zapf dingbats';
33346 
33347 			var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
33348 
33349 			each(fonts, function(font) {
33350 				items.push({
33351 					text: {raw: font[0]},
33352 					value: font[1],
33353 					textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
33354 				});
33355 			});
33356 
33357 			return {
33358 				type: 'listbox',
33359 				text: 'Font Family',
33360 				tooltip: 'Font Family',
33361 				values: items,
33362 				fixedWidth: true,
33363 				onPostRender: createListBoxChangeHandler(items, 'fontname'),
33364 				onselect: function(e) {
33365 					if (e.control.settings.value) {
33366 						editor.execCommand('FontName', false, e.control.settings.value);
33367 					}
33368 				}
33369 			};
33370 		});
33371 
33372 		editor.addButton('fontsizeselect', function() {
33373 			var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
33374 			var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
33375 
33376 			each(fontsize_formats.split(' '), function(item) {
33377 				var text = item, value = item;
33378 				// Allow text=value font sizes.
33379 				var values = item.split('=');
33380 				if (values.length > 1) {
33381 					text = values[0];
33382 					value = values[1];
33383 				}
33384 				items.push({text: text, value: value});
33385 			});
33386 
33387 			return {
33388 				type: 'listbox',
33389 				text: 'Font Sizes',
33390 				tooltip: 'Font Sizes',
33391 				values: items,
33392 				fixedWidth: true,
33393 				onPostRender: createListBoxChangeHandler(items, 'fontsize'),
33394 				onclick: function(e) {
33395 					if (e.control.settings.value) {
33396 						editor.execCommand('FontSize', false, e.control.settings.value);
33397 					}
33398 				}
33399 			};
33400 		});
33401 
33402 		editor.addMenuItem('formats', {
33403 			text: 'Formats',
33404 			menu: formatMenu
33405 		});
33406 	}
33407 });
33408 
33409 // Included from: js/tinymce/classes/ui/GridLayout.js
33410 
33411 /**
33412  * GridLayout.js
33413  *
33414  * Copyright, Moxiecode Systems AB
33415  * Released under LGPL License.
33416  *
33417  * License: http://www.tinymce.com/license
33418  * Contributing: http://www.tinymce.com/contributing
33419  */
33420 
33421 /**
33422  * This layout manager places controls in a grid.
33423  *
33424  * @setting {Number} spacing Spacing between controls.
33425  * @setting {Number} spacingH Horizontal spacing between controls.
33426  * @setting {Number} spacingV Vertical spacing between controls.
33427  * @setting {Number} columns Number of columns to use.
33428  * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
33429  * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
33430  * @setting {String} pack start|end
33431  *
33432  * @class tinymce.ui.GridLayout
33433  * @extends tinymce.ui.AbsoluteLayout
33434  */
33435 define("tinymce/ui/GridLayout", [
33436 	"tinymce/ui/AbsoluteLayout"
33437 ], function(AbsoluteLayout) {
33438 	"use strict";
33439 
33440 	return AbsoluteLayout.extend({
33441 		/**
33442 		 * Recalculates the positions of the controls in the specified container.
33443 		 *
33444 		 * @method recalc
33445 		 * @param {tinymce.ui.Container} container Container instance to recalc.
33446 		 */
33447 		recalc: function(container) {
33448 			var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
33449 				ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
33450 				colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;
33451 
33452 			// Get layout settings
33453 			settings = container.settings;
33454 			items = container.items().filter(':visible');
33455 			contLayoutRect = container.layoutRect();
33456 			cols = settings.columns || Math.ceil(Math.sqrt(items.length));
33457 			rows = Math.ceil(items.length / cols);
33458 			spacingH = settings.spacingH || settings.spacing || 0;
33459 			spacingV = settings.spacingV || settings.spacing || 0;
33460 			alignH = settings.alignH || settings.align;
33461 			alignV = settings.alignV || settings.align;
33462 			contPaddingBox = container._paddingBox;
33463 			reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();
33464 
33465 			if (alignH && typeof(alignH) == "string") {
33466 				alignH = [alignH];
33467 			}
33468 
33469 			if (alignV && typeof(alignV) == "string") {
33470 				alignV = [alignV];
33471 			}
33472 
33473 			// Zero padd columnWidths
33474 			for (x = 0; x < cols; x++) {
33475 				colWidths.push(0);
33476 			}
33477 
33478 			// Zero padd rowHeights
33479 			for (y = 0; y < rows; y++) {
33480 				rowHeights.push(0);
33481 			}
33482 
33483 			// Calculate columnWidths and rowHeights
33484 			for (y = 0; y < rows; y++) {
33485 				for (x = 0; x < cols; x++) {
33486 					ctrl = items[y * cols + x];
33487 
33488 					// Out of bounds
33489 					if (!ctrl) {
33490 						break;
33491 					}
33492 
33493 					ctrlLayoutRect = ctrl.layoutRect();
33494 					ctrlMinWidth = ctrlLayoutRect.minW;
33495 					ctrlMinHeight = ctrlLayoutRect.minH;
33496 
33497 					colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
33498 					rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
33499 				}
33500 			}
33501 
33502 			// Calculate maxX
33503 			availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
33504 			for (maxX = 0, x = 0; x < cols; x++) {
33505 				maxX += colWidths[x] + (x > 0 ? spacingH : 0);
33506 				availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
33507 			}
33508 
33509 			// Calculate maxY
33510 			availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
33511 			for (maxY = 0, y = 0; y < rows; y++) {
33512 				maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
33513 				availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
33514 			}
33515 
33516 			maxX += contPaddingBox.left + contPaddingBox.right;
33517 			maxY += contPaddingBox.top + contPaddingBox.bottom;
33518 
33519 			// Calculate minW/minH
33520 			rect = {};
33521 			rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
33522 			rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
33523 
33524 			rect.contentW = rect.minW - contLayoutRect.deltaW;
33525 			rect.contentH = rect.minH - contLayoutRect.deltaH;
33526 			rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
33527 			rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
33528 			rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
33529 			rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
33530 
33531 			// Resize container container if minSize was changed
33532 			if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
33533 				rect.w = rect.minW;
33534 				rect.h = rect.minH;
33535 
33536 				container.layoutRect(rect);
33537 				this.recalc(container);
33538 
33539 				// Forced recalc for example if items are hidden/shown
33540 				if (container._lastRect === null) {
33541 					var parentCtrl = container.parent();
33542 					if (parentCtrl) {
33543 						parentCtrl._lastRect = null;
33544 						parentCtrl.recalc();
33545 					}
33546 				}
33547 
33548 				return;
33549 			}
33550 
33551 			// Update contentW/contentH so absEnd moves correctly
33552 			if (contLayoutRect.autoResize) {
33553 				rect = container.layoutRect(rect);
33554 				rect.contentW = rect.minW - contLayoutRect.deltaW;
33555 				rect.contentH = rect.minH - contLayoutRect.deltaH;
33556 			}
33557 
33558 			var flexV;
33559 
33560 			if (settings.packV == 'start') {
33561 				flexV = 0;
33562 			} else {
33563 				flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
33564 			}
33565 
33566 			// Calculate totalFlex
33567 			var totalFlex = 0;
33568 			var flexWidths = settings.flexWidths;
33569 			if (flexWidths) {
33570 				for (x = 0; x < flexWidths.length; x++) {
33571 					totalFlex += flexWidths[x];
33572 				}
33573 			} else {
33574 				totalFlex = cols;
33575 			}
33576 
33577 			// Calculate new column widths based on flex values
33578 			var ratio = availableWidth / totalFlex;
33579 			for (x = 0; x < cols; x++) {
33580 				colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
33581 			}
33582 
33583 			// Move/resize controls
33584 			posY = contPaddingBox.top;
33585 			for (y = 0; y < rows; y++) {
33586 				posX = contPaddingBox.left;
33587 				height = rowHeights[y] + flexV;
33588 
33589 				for (x = 0; x < cols; x++) {
33590 					if (reverseRows) {
33591 						idx = y * cols + cols - 1 - x;
33592 					} else {
33593 						idx = y * cols + x;
33594 					}
33595 
33596 					ctrl = items[idx];
33597 
33598 					// No more controls to render then break
33599 					if (!ctrl) {
33600 						break;
33601 					}
33602 
33603 					// Get control settings and calculate x, y
33604 					ctrlSettings = ctrl.settings;
33605 					ctrlLayoutRect = ctrl.layoutRect();
33606 					width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
33607 					ctrlLayoutRect.x = posX;
33608 					ctrlLayoutRect.y = posY;
33609 
33610 					// Align control horizontal
33611 					align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
33612 					if (align == "center") {
33613 						ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
33614 					} else if (align == "right") {
33615 						ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
33616 					} else if (align == "stretch") {
33617 						ctrlLayoutRect.w = width;
33618 					}
33619 
33620 					// Align control vertical
33621 					align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
33622 					if (align == "center") {
33623 						ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
33624 					} else if (align == "bottom") {
33625 						ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
33626 					} else if (align == "stretch") {
33627 						ctrlLayoutRect.h = height;
33628 					}
33629 
33630 					ctrl.layoutRect(ctrlLayoutRect);
33631 
33632 					posX += width + spacingH;
33633 
33634 					if (ctrl.recalc) {
33635 						ctrl.recalc();
33636 					}
33637 				}
33638 
33639 				posY += height + spacingV;
33640 			}
33641 		}
33642 	});
33643 });
33644 
33645 // Included from: js/tinymce/classes/ui/Iframe.js
33646 
33647 /**
33648  * Iframe.js
33649  *
33650  * Copyright, Moxiecode Systems AB
33651  * Released under LGPL License.
33652  *
33653  * License: http://www.tinymce.com/license
33654  * Contributing: http://www.tinymce.com/contributing
33655  */
33656 
33657 /*jshint scripturl:true */
33658 
33659 /**
33660  * This class creates an iframe.
33661  *
33662  * @setting {String} url Url to open in the iframe.
33663  *
33664  * @-x-less Iframe.less
33665  * @class tinymce.ui.Iframe
33666  * @extends tinymce.ui.Widget
33667  */
33668 define("tinymce/ui/Iframe", [
33669 	"tinymce/ui/Widget"
33670 ], function(Widget) {
33671 	"use strict";
33672 
33673 	return Widget.extend({
33674 		/**
33675 		 * Renders the control as a HTML string.
33676 		 *
33677 		 * @method renderHtml
33678 		 * @return {String} HTML representing the control.
33679 		 */
33680 		renderHtml: function() {
33681 			var self = this;
33682 
33683 			self.addClass('iframe');
33684 			self.canFocus = false;
33685 
33686 			/*eslint no-script-url:0 */
33687 			return (
33688 				'<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
33689 				(self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
33690 			);
33691 		},
33692 
33693 		/**
33694 		 * Setter for the iframe source.
33695 		 *
33696 		 * @method src
33697 		 * @param {String} src Source URL for iframe.
33698 		 */
33699 		src: function(src) {
33700 			this.getEl().src = src;
33701 		},
33702 
33703 		/**
33704 		 * Inner HTML for the iframe.
33705 		 *
33706 		 * @method html
33707 		 * @param {String} html HTML string to set as HTML inside the iframe.
33708 		 * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
33709 		 * @return {tinymce.ui.Iframe} Current iframe control.
33710 		 */
33711 		html: function(html, callback) {
33712 			var self = this, body = this.getEl().contentWindow.document.body;
33713 
33714 			// Wait for iframe to initialize IE 10 takes time
33715 			if (!body) {
33716 				setTimeout(function() {
33717 					self.html(html);
33718 				}, 0);
33719 			} else {
33720 				body.innerHTML = html;
33721 
33722 				if (callback) {
33723 					callback();
33724 				}
33725 			}
33726 
33727 			return this;
33728 		}
33729 	});
33730 });
33731 
33732 // Included from: js/tinymce/classes/ui/Label.js
33733 
33734 /**
33735  * Label.js
33736  *
33737  * Copyright, Moxiecode Systems AB
33738  * Released under LGPL License.
33739  *
33740  * License: http://www.tinymce.com/license
33741  * Contributing: http://www.tinymce.com/contributing
33742  */
33743 
33744 /**
33745  * This class creates a label element. A label is a simple text control
33746  * that can be bound to other controls.
33747  *
33748  * @-x-less Label.less
33749  * @class tinymce.ui.Label
33750  * @extends tinymce.ui.Widget
33751  */
33752 define("tinymce/ui/Label", [
33753 	"tinymce/ui/Widget",
33754 	"tinymce/ui/DomUtils"
33755 ], function(Widget, DomUtils) {
33756 	"use strict";
33757 
33758 	return Widget.extend({
33759 		/**
33760 		 * Constructs a instance with the specified settings.
33761 		 *
33762 		 * @constructor
33763 		 * @param {Object} settings Name/value object with settings.
33764 		 * @param {Boolean} multiline Multiline label.
33765 		 */
33766 		init: function(settings) {
33767 			var self = this;
33768 
33769 			self._super(settings);
33770 			self.addClass('widget');
33771 			self.addClass('label');
33772 			self.canFocus = false;
33773 
33774 			if (settings.multiline) {
33775 				self.addClass('autoscroll');
33776 			}
33777 
33778 			if (settings.strong) {
33779 				self.addClass('strong');
33780 			}
33781 		},
33782 
33783 		/**
33784 		 * Initializes the current controls layout rect.
33785 		 * This will be executed by the layout managers to determine the
33786 		 * default minWidth/minHeight etc.
33787 		 *
33788 		 * @method initLayoutRect
33789 		 * @return {Object} Layout rect instance.
33790 		 */
33791 		initLayoutRect: function() {
33792 			var self = this, layoutRect = self._super();
33793 
33794 			if (self.settings.multiline) {
33795 				var size = DomUtils.getSize(self.getEl());
33796 
33797 				// Check if the text fits within maxW if not then try word wrapping it
33798 				if (size.width > layoutRect.maxW) {
33799 					layoutRect.minW = layoutRect.maxW;
33800 					self.addClass('multiline');
33801 				}
33802 
33803 				self.getEl().style.width = layoutRect.minW + 'px';
33804 				layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
33805 			}
33806 
33807 			return layoutRect;
33808 		},
33809 
33810 		/**
33811 		 * Repaints the control after a layout operation.
33812 		 *
33813 		 * @method repaint
33814 		 */
33815 		repaint: function() {
33816 			var self = this;
33817 
33818 			if (!self.settings.multiline) {
33819 				self.getEl().style.lineHeight = self.layoutRect().h + 'px';
33820 			}
33821 
33822 			return self._super();
33823 		},
33824 
33825 		/**
33826 		 * Sets/gets the current label text.
33827 		 *
33828 		 * @method text
33829 		 * @param {String} [text] New label text.
33830 		 * @return {String|tinymce.ui.Label} Current text or current label instance.
33831 		 */
33832 		text: function(text) {
33833 			var self = this;
33834 
33835 			if (self._rendered && text) {
33836 				this.innerHtml(self.encode(text));
33837 			}
33838 
33839 			return self._super(text);
33840 		},
33841 
33842 		/**
33843 		 * Renders the control as a HTML string.
33844 		 *
33845 		 * @method renderHtml
33846 		 * @return {String} HTML representing the control.
33847 		 */
33848 		renderHtml: function() {
33849 			var self = this, forId = self.settings.forId;
33850 
33851 			return (
33852 				'<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
33853 					self.encode(self._text) +
33854 				'</label>'
33855 			);
33856 		}
33857 	});
33858 });
33859 
33860 // Included from: js/tinymce/classes/ui/MenuButton.js
33861 
33862 /**
33863  * MenuButton.js
33864  *
33865  * Copyright, Moxiecode Systems AB
33866  * Released under LGPL License.
33867  *
33868  * License: http://www.tinymce.com/license
33869  * Contributing: http://www.tinymce.com/contributing
33870  */
33871 
33872 /**
33873  * Creates a new menu button.
33874  *
33875  * @-x-less MenuButton.less
33876  * @class tinymce.ui.MenuButton
33877  * @extends tinymce.ui.Button
33878  */
33879 define("tinymce/ui/MenuButton", [
33880 	"tinymce/ui/Button",
33881 	"tinymce/ui/Factory"
33882 ], function(Button, Factory) {
33883 	"use strict";
33884 
33885 	// TODO: Maybe add as some global function
33886 	function isChildOf(node, parent) {
33887 		while (node) {
33888 			if (parent === node) {
33889 				return true;
33890 			}
33891 
33892 			node = node.parentNode;
33893 		}
33894 
33895 		return false;
33896 	}
33897 
33898 	var MenuButton = Button.extend({
33899 		/**
33900 		 * Constructs a instance with the specified settings.
33901 		 *
33902 		 * @constructor
33903 		 * @param {Object} settings Name/value object with settings.
33904 		 */
33905 		init: function(settings) {
33906 			var self = this;
33907 
33908 			self._renderOpen = true;
33909 			self._super(settings);
33910 
33911 			self.addClass('menubtn');
33912 
33913 			if (settings.fixedWidth) {
33914 				self.addClass('fixed-width');
33915 			}
33916 
33917 			self.aria('haspopup', true);
33918 			self.hasPopup = true;
33919 		},
33920 
33921 		/**
33922 		 * Shows the menu for the button.
33923 		 *
33924 		 * @method showMenu
33925 		 */
33926 		showMenu: function() {
33927 			var self = this, settings = self.settings, menu;
33928 
33929 			if (self.menu && self.menu.visible()) {
33930 				return self.hideMenu();
33931 			}
33932 
33933 			if (!self.menu) {
33934 				menu = settings.menu || [];
33935 
33936 				// Is menu array then auto constuct menu control
33937 				if (menu.length) {
33938 					menu = {
33939 						type: 'menu',
33940 						items: menu
33941 					};
33942 				} else {
33943 					menu.type = menu.type || 'menu';
33944 				}
33945 
33946 				self.menu = Factory.create(menu).parent(self).renderTo();
33947 				self.fire('createmenu');
33948 				self.menu.reflow();
33949 				self.menu.on('cancel', function(e) {
33950 					if (e.control.parent() === self.menu) {
33951 						e.stopPropagation();
33952 						e.preventDefault();
33953 						self.focus();
33954 						self.hideMenu();
33955 					}
33956 				});
33957 
33958 				// Move focus to button when a menu item is selected/clicked
33959 				self.menu.on('select', function() {
33960 					self.focus();
33961 				});
33962 
33963 				self.menu.on('show hide', function(e) {
33964 					if (e.control == self.menu) {
33965 						self.activeMenu(e.type == 'show');
33966 					}
33967 
33968 					self.aria('expanded', e.type == 'show');
33969 				}).fire('show');
33970 			}
33971 
33972 			self.menu.show();
33973 			self.menu.layoutRect({w: self.layoutRect().w});
33974 			self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
33975 		},
33976 
33977 		/**
33978 		 * Hides the menu for the button.
33979 		 *
33980 		 * @method hideMenu
33981 		 */
33982 		hideMenu: function() {
33983 			var self = this;
33984 
33985 			if (self.menu) {
33986 				self.menu.items().each(function(item) {
33987 					if (item.hideMenu) {
33988 						item.hideMenu();
33989 					}
33990 				});
33991 
33992 				self.menu.hide();
33993 			}
33994 		},
33995 
33996 		/**
33997 		 * Sets the active menu state.
33998 		 *
33999 		 * @private
34000 		 */
34001 		activeMenu: function(state) {
34002 			this.toggleClass('active', state);
34003 		},
34004 
34005 		/**
34006 		 * Renders the control as a HTML string.
34007 		 *
34008 		 * @method renderHtml
34009 		 * @return {String} HTML representing the control.
34010 		 */
34011 		renderHtml: function() {
34012 			var self = this, id = self._id, prefix = self.classPrefix;
34013 			var icon = self.settings.icon, image;
34014 
34015 			image = self.settings.image;
34016 			if (image) {
34017 				icon = 'none';
34018 
34019 				// Support for [high dpi, low dpi] image sources
34020 				if (typeof image != "string") {
34021 					image = window.getSelection ? image[0] : image[1];
34022 				}
34023 
34024 				image = ' style="background-image: url(\'' + image + '\')"';
34025 			} else {
34026 				image = '';
34027 			}
34028 
34029 			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
34030 
34031 			var parentrolemap = {
34032 				buttongroup: 'button',
34033 				toolbar: 'button',
34034 				menubar: 'menuitem'
34035 			};
34036 			self.aria('role', parentrolemap[self.parent().type] || 'combobox');
34037 
34038 			return (
34039 				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
34040 					'<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
34041 						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
34042 						'<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
34043 						' <i class="' + prefix + 'caret"></i>' +
34044 					'</button>' +
34045 				'</div>'
34046 			);
34047 		},
34048 
34049 		/**
34050 		 * Gets invoked after the control has been rendered.
34051 		 *
34052 		 * @method postRender
34053 		 */
34054 		postRender: function() {
34055 			var self = this;
34056 
34057 			self.on('click', function(e) {
34058 				if (e.control === self && isChildOf(e.target, self.getEl())) {
34059 					self.showMenu();
34060 
34061 					if (e.aria) {
34062 						self.menu.items()[0].focus();
34063 					}
34064 				}
34065 			});
34066 
34067 			self.on('mouseenter', function(e) {
34068 				var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
34069 
34070 				if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
34071 					parent.items().filter('MenuButton').each(function(ctrl) {
34072 						if (ctrl.hideMenu && ctrl != overCtrl) {
34073 							if (ctrl.menu && ctrl.menu.visible()) {
34074 								hasVisibleSiblingMenu = true;
34075 							}
34076 
34077 							ctrl.hideMenu();
34078 						}
34079 					});
34080 
34081 					if (hasVisibleSiblingMenu) {
34082 						overCtrl.focus(); // Fix for: #5887
34083 						overCtrl.showMenu();
34084 					}
34085 				}
34086 			});
34087 
34088 			return self._super();
34089 		},
34090 
34091 		/**
34092 		 * Sets/gets the current button text.
34093 		 *
34094 		 * @method text
34095 		 * @param {String} [text] New button text.
34096 		 * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance.
34097 		 */
34098 		text: function(text) {
34099 			var self = this, i, children;
34100 
34101 			if (self._rendered) {
34102 				children = self.getEl('open').getElementsByTagName('span');
34103 				for (i = 0; i < children.length; i++) {
34104 					children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
34105 				}
34106 			}
34107 
34108 			return this._super(text);
34109 		},
34110 
34111 		/**
34112 		 * Removes the control and it's menus.
34113 		 *
34114 		 * @method remove
34115 		 */
34116 		remove: function() {
34117 			this._super();
34118 
34119 			if (this.menu) {
34120 				this.menu.remove();
34121 			}
34122 		}
34123 	});
34124 
34125 	return MenuButton;
34126 });
34127 
34128 // Included from: js/tinymce/classes/ui/ListBox.js
34129 
34130 /**
34131  * ListBox.js
34132  *
34133  * Copyright, Moxiecode Systems AB
34134  * Released under LGPL License.
34135  *
34136  * License: http://www.tinymce.com/license
34137  * Contributing: http://www.tinymce.com/contributing
34138  */
34139 
34140 /**
34141  * Creates a new list box control.
34142  *
34143  * @-x-less ListBox.less
34144  * @class tinymce.ui.ListBox
34145  * @extends tinymce.ui.MenuButton
34146  */
34147 define("tinymce/ui/ListBox", [
34148 	"tinymce/ui/MenuButton"
34149 ], function(MenuButton) {
34150 	"use strict";
34151 
34152 	return MenuButton.extend({
34153 		/**
34154 		 * Constructs a instance with the specified settings.
34155 		 *
34156 		 * @constructor
34157 		 * @param {Object} settings Name/value object with settings.
34158 		 * @setting {Array} values Array with values to add to list box.
34159 		 */
34160 		init: function(settings) {
34161 			var self = this, values, selected, selectedText, lastItemCtrl;
34162 
34163 			function setSelected(menuValues) {
34164 				// Try to find a selected value
34165 				for (var i = 0; i < menuValues.length; i++) {
34166 					selected = menuValues[i].selected || settings.value === menuValues[i].value;
34167 
34168 					if (selected) {
34169 						selectedText = selectedText || menuValues[i].text;
34170 						self._value = menuValues[i].value;
34171 						break;
34172 					}
34173 
34174 					// If the value has a submenu, try to find the selected values in that menu
34175 					if (menuValues[i].menu) {
34176 						setSelected(menuValues[i].menu);
34177 					}
34178 				}
34179 			}
34180 
34181 			self._values = values = settings.values;
34182 			if (values) {
34183 				if (typeof settings.value != "undefined") {
34184 					setSelected(values);
34185 				}
34186 
34187 				// Default with first item
34188 				if (!selected && values.length > 0) {
34189 					selectedText = values[0].text;
34190 					self._value = values[0].value;
34191 				}
34192 
34193 				settings.menu = values;
34194 			}
34195 
34196 			settings.text = settings.text || selectedText || values[0].text;
34197 
34198 			self._super(settings);
34199 			self.addClass('listbox');
34200 
34201 			self.on('select', function(e) {
34202 				var ctrl = e.control;
34203 
34204 				if (lastItemCtrl) {
34205 					e.lastControl = lastItemCtrl;
34206 				}
34207 
34208 				if (settings.multiple) {
34209 					ctrl.active(!ctrl.active());
34210 				} else {
34211 					self.value(e.control.settings.value);
34212 				}
34213 
34214 				lastItemCtrl = ctrl;
34215 			});
34216 		},
34217 
34218 		/**
34219 		 * Getter/setter function for the control value.
34220 		 *
34221 		 * @method value
34222 		 * @param {String} [value] Value to be set.
34223 		 * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
34224 		 */
34225 		value: function(value) {
34226 			var self = this, active, selectedText, menu;
34227 
34228 			function activateByValue(menu, value) {
34229 				menu.items().each(function(ctrl) {
34230 					active = ctrl.value() === value;
34231 
34232 					if (active) {
34233 						selectedText = selectedText || ctrl.text();
34234 					}
34235 
34236 					ctrl.active(active);
34237 
34238 					if (ctrl.menu) {
34239 						activateByValue(ctrl.menu, value);
34240 					}
34241 				});
34242 			}
34243 
34244 			function setActiveValues(menuValues) {
34245 				for (var i = 0; i < menuValues.length; i++) {
34246 					active = menuValues[i].value == value;
34247 
34248 					if (active) {
34249 						selectedText = selectedText || menuValues[i].text;
34250 					}
34251 
34252 					menuValues[i].active = active;
34253 
34254 					if (menuValues[i].menu) {
34255 						setActiveValues(menuValues[i].menu);
34256 					}
34257 				}
34258 			}
34259 
34260 			if (typeof(value) != "undefined") {
34261 				if (self.menu) {
34262 					activateByValue(self.menu, value);
34263 				} else {
34264 					menu = self.settings.menu;
34265 					setActiveValues(menu);
34266 				}
34267 
34268 				self.text(selectedText || this.settings.text);
34269 			}
34270 
34271 			return self._super(value);
34272 		}
34273 	});
34274 });
34275 
34276 // Included from: js/tinymce/classes/ui/MenuItem.js
34277 
34278 /**
34279  * MenuItem.js
34280  *
34281  * Copyright, Moxiecode Systems AB
34282  * Released under LGPL License.
34283  *
34284  * License: http://www.tinymce.com/license
34285  * Contributing: http://www.tinymce.com/contributing
34286  */
34287 
34288 /**
34289  * Creates a new menu item.
34290  *
34291  * @-x-less MenuItem.less
34292  * @class tinymce.ui.MenuItem
34293  * @extends tinymce.ui.Widget
34294  */
34295 define("tinymce/ui/MenuItem", [
34296 	"tinymce/ui/Widget",
34297 	"tinymce/ui/Factory",
34298 	"tinymce/Env"
34299 ], function(Widget, Factory, Env) {
34300 	"use strict";
34301 
34302 	return Widget.extend({
34303 		Defaults: {
34304 			border: 0,
34305 			role: 'menuitem'
34306 		},
34307 
34308 		/**
34309 		 * Constructs a instance with the specified settings.
34310 		 *
34311 		 * @constructor
34312 		 * @param {Object} settings Name/value object with settings.
34313 		 * @setting {Boolean} selectable Selectable menu.
34314 		 * @setting {Array} menu Submenu array with items.
34315 		 * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
34316 		 */
34317 		init: function(settings) {
34318 			var self = this;
34319 
34320 			self.hasPopup = true;
34321 
34322 			self._super(settings);
34323 
34324 			settings = self.settings;
34325 
34326 			self.addClass('menu-item');
34327 
34328 			if (settings.menu) {
34329 				self.addClass('menu-item-expand');
34330 			}
34331 
34332 			if (settings.preview) {
34333 				self.addClass('menu-item-preview');
34334 			}
34335 
34336 			if (self._text === '-' || self._text === '|') {
34337 				self.addClass('menu-item-sep');
34338 				self.aria('role', 'separator');
34339 				self._text = '-';
34340 			}
34341 
34342 			if (settings.selectable) {
34343 				self.aria('role', 'menuitemcheckbox');
34344 				self.addClass('menu-item-checkbox');
34345 				settings.icon = 'selected';
34346 			}
34347 
34348 			if (!settings.preview && !settings.selectable) {
34349 				self.addClass('menu-item-normal');
34350 			}
34351 
34352 			self.on('mousedown', function(e) {
34353 				e.preventDefault();
34354 			});
34355 
34356 			if (settings.menu && !settings.ariaHideMenu) {
34357 				self.aria('haspopup', true);
34358 			}
34359 		},
34360 
34361 		/**
34362 		 * Returns true/false if the menuitem has sub menu.
34363 		 *
34364 		 * @method hasMenus
34365 		 * @return {Boolean} True/false state if it has submenu.
34366 		 */
34367 		hasMenus: function() {
34368 			return !!this.settings.menu;
34369 		},
34370 
34371 		/**
34372 		 * Shows the menu for the menu item.
34373 		 *
34374 		 * @method showMenu
34375 		 */
34376 		showMenu: function() {
34377 			var self = this, settings = self.settings, menu, parent = self.parent();
34378 
34379 			parent.items().each(function(ctrl) {
34380 				if (ctrl !== self) {
34381 					ctrl.hideMenu();
34382 				}
34383 			});
34384 
34385 			if (settings.menu) {
34386 				menu = self.menu;
34387 
34388 				if (!menu) {
34389 					menu = settings.menu;
34390 
34391 					// Is menu array then auto constuct menu control
34392 					if (menu.length) {
34393 						menu = {
34394 							type: 'menu',
34395 							items: menu
34396 						};
34397 					} else {
34398 						menu.type = menu.type || 'menu';
34399 					}
34400 
34401 					if (parent.settings.itemDefaults) {
34402 						menu.itemDefaults = parent.settings.itemDefaults;
34403 					}
34404 
34405 					menu = self.menu = Factory.create(menu).parent(self).renderTo();
34406 					menu.reflow();
34407 					menu.on('cancel', function(e) {
34408 						e.stopPropagation();
34409 						self.focus();
34410 						menu.hide();
34411 					});
34412 					menu.on('show hide', function(e) {
34413 						e.control.items().each(function(ctrl) {
34414 							ctrl.active(ctrl.settings.selected);
34415 						});
34416 					}).fire('show');
34417 
34418 					menu.on('hide', function(e) {
34419 						if (e.control === menu) {
34420 							self.removeClass('selected');
34421 						}
34422 					});
34423 
34424 					menu.submenu = true;
34425 				} else {
34426 					menu.show();
34427 				}
34428 
34429 				menu._parentMenu = parent;
34430 
34431 				menu.addClass('menu-sub');
34432 
34433 				var rel = menu.testMoveRel(
34434 					self.getEl(),
34435 					self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
34436 				);
34437 
34438 				menu.moveRel(self.getEl(), rel);
34439 				menu.rel = rel;
34440 
34441 				rel = 'menu-sub-' + rel;
34442 				menu.removeClass(menu._lastRel);
34443 				menu.addClass(rel);
34444 				menu._lastRel = rel;
34445 
34446 				self.addClass('selected');
34447 				self.aria('expanded', true);
34448 			}
34449 		},
34450 
34451 		/**
34452 		 * Hides the menu for the menu item.
34453 		 *
34454 		 * @method hideMenu
34455 		 */
34456 		hideMenu: function() {
34457 			var self = this;
34458 
34459 			if (self.menu) {
34460 				self.menu.items().each(function(item) {
34461 					if (item.hideMenu) {
34462 						item.hideMenu();
34463 					}
34464 				});
34465 
34466 				self.menu.hide();
34467 				self.aria('expanded', false);
34468 			}
34469 
34470 			return self;
34471 		},
34472 
34473 		/**
34474 		 * Renders the control as a HTML string.
34475 		 *
34476 		 * @method renderHtml
34477 		 * @return {String} HTML representing the control.
34478 		 */
34479 		renderHtml: function() {
34480 			var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
34481 			var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
34482 
34483 			if (icon) {
34484 				self.parent().addClass('menu-has-icons');
34485 			}
34486 
34487 			if (settings.image) {
34488 				icon = 'none';
34489 				image = ' style="background-image: url(\'' + settings.image + '\')"';
34490 			}
34491 
34492 			if (shortcut && Env.mac) {
34493 				// format shortcut for Mac
34494 				shortcut = shortcut.replace(/ctrl\+alt\+/i, '⌥⌘'); // ctrl+cmd
34495 				shortcut = shortcut.replace(/ctrl\+/i, '⌘'); // ctrl symbol
34496 				shortcut = shortcut.replace(/alt\+/i, '⌥'); // cmd symbol
34497 				shortcut = shortcut.replace(/shift\+/i, '⇧'); // shift symbol
34498 			}
34499 
34500 			icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
34501 
34502 			return (
34503 				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
34504 					(text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
34505 					(text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
34506 					(shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
34507 					(settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
34508 				'</div>'
34509 			);
34510 		},
34511 
34512 		/**
34513 		 * Gets invoked after the control has been rendered.
34514 		 *
34515 		 * @method postRender
34516 		 */
34517 		postRender: function() {
34518 			var self = this, settings = self.settings;
34519 
34520 			var textStyle = settings.textStyle;
34521 			if (typeof(textStyle) == "function") {
34522 				textStyle = textStyle.call(this);
34523 			}
34524 
34525 			if (textStyle) {
34526 				var textElm = self.getEl('text');
34527 				if (textElm) {
34528 					textElm.setAttribute('style', textStyle);
34529 				}
34530 			}
34531 
34532 			self.on('mouseenter click', function(e) {
34533 				if (e.control === self) {
34534 					if (!settings.menu && e.type === 'click') {
34535 						self.fire('select');
34536 						self.parent().hideAll();
34537 					} else {
34538 						self.showMenu();
34539 
34540 						if (e.aria) {
34541 							self.menu.focus(true);
34542 						}
34543 					}
34544 				}
34545 			});
34546 
34547 			self._super();
34548 
34549 			return self;
34550 		},
34551 
34552 		active: function(state) {
34553 			if (typeof(state) != "undefined") {
34554 				this.aria('checked', state);
34555 			}
34556 
34557 			return this._super(state);
34558 		},
34559 
34560 		/**
34561 		 * Removes the control and it's menus.
34562 		 *
34563 		 * @method remove
34564 		 */
34565 		remove: function() {
34566 			this._super();
34567 
34568 			if (this.menu) {
34569 				this.menu.remove();
34570 			}
34571 		}
34572 	});
34573 });
34574 
34575 // Included from: js/tinymce/classes/ui/Menu.js
34576 
34577 /**
34578  * Menu.js
34579  *
34580  * Copyright, Moxiecode Systems AB
34581  * Released under LGPL License.
34582  *
34583  * License: http://www.tinymce.com/license
34584  * Contributing: http://www.tinymce.com/contributing
34585  */
34586 
34587 /**
34588  * Creates a new menu.
34589  *
34590  * @-x-less Menu.less
34591  * @class tinymce.ui.Menu
34592  * @extends tinymce.ui.FloatPanel
34593  */
34594 define("tinymce/ui/Menu", [
34595 	"tinymce/ui/FloatPanel",
34596 	"tinymce/ui/MenuItem",
34597 	"tinymce/util/Tools"
34598 ], function(FloatPanel, MenuItem, Tools) {
34599 	"use strict";
34600 
34601 	var Menu = FloatPanel.extend({
34602 		Defaults: {
34603 			defaultType: 'menuitem',
34604 			border: 1,
34605 			layout: 'stack',
34606 			role: 'application',
34607 			bodyRole: 'menu',
34608 			ariaRoot: true
34609 		},
34610 
34611 		/**
34612 		 * Constructs a instance with the specified settings.
34613 		 *
34614 		 * @constructor
34615 		 * @param {Object} settings Name/value object with settings.
34616 		 */
34617 		init: function(settings) {
34618 			var self = this;
34619 
34620 			settings.autohide = true;
34621 			settings.constrainToViewport = true;
34622 
34623 			if (settings.itemDefaults) {
34624 				var items = settings.items, i = items.length;
34625 
34626 				while (i--) {
34627 					items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
34628 				}
34629 			}
34630 
34631 			self._super(settings);
34632 			self.addClass('menu');
34633 		},
34634 
34635 		/**
34636 		 * Repaints the control after a layout operation.
34637 		 *
34638 		 * @method repaint
34639 		 */
34640 		repaint: function() {
34641 			this.toggleClass('menu-align', true);
34642 
34643 			this._super();
34644 
34645 			this.getEl().style.height = '';
34646 			this.getEl('body').style.height = '';
34647 
34648 			return this;
34649 		},
34650 
34651 		/**
34652 		 * Hides/closes the menu.
34653 		 *
34654 		 * @method cancel
34655 		 */
34656 		cancel: function() {
34657 			var self = this;
34658 
34659 			self.hideAll();
34660 			self.fire('select');
34661 		},
34662 
34663 		/**
34664 		 * Hide menu and all sub menus.
34665 		 *
34666 		 * @method hideAll
34667 		 */
34668 		hideAll: function() {
34669 			var self = this;
34670 
34671 			this.find('menuitem').exec('hideMenu');
34672 
34673 			return self._super();
34674 		},
34675 /*
34676 		getContainerElm: function() {
34677 			var doc = document, id = this.classPrefix + 'menucontainer';
34678 
34679 			var elm = doc.getElementById(id);
34680 			if (!elm) {
34681 				elm = doc.createElement('div');
34682 				elm.id = id;
34683 				elm.setAttribute('role', 'application');
34684 				elm.className = this.classPrefix + '-reset';
34685 				elm.style.position = 'absolute';
34686 				elm.style.top = elm.style.left = '0';
34687 				elm.style.overflow = 'visible';
34688 				doc.body.appendChild(elm);
34689 			}
34690 
34691 			return elm;
34692 		},
34693 */
34694 		/**
34695 		 * Invoked before the menu is rendered.
34696 		 *
34697 		 * @method preRender
34698 		 */
34699 		preRender: function() {
34700 			var self = this;
34701 
34702 			self.items().each(function(ctrl) {
34703 				var settings = ctrl.settings;
34704 
34705 				if (settings.icon || settings.selectable) {
34706 					self._hasIcons = true;
34707 					return false;
34708 				}
34709 			});
34710 
34711 			return self._super();
34712 		}
34713 	});
34714 
34715 	return Menu;
34716 });
34717 
34718 // Included from: js/tinymce/classes/ui/Toolbar.js
34719 
34720 /**
34721  * Toolbar.js
34722  *
34723  * Copyright, Moxiecode Systems AB
34724  * Released under LGPL License.
34725  *
34726  * License: http://www.tinymce.com/license
34727  * Contributing: http://www.tinymce.com/contributing
34728  */
34729 
34730 /**
34731  * Creates a new toolbar.
34732  *
34733  * @class tinymce.ui.Toolbar
34734  * @extends tinymce.ui.Container
34735  */
34736 define("tinymce/ui/Toolbar", [
34737 	"tinymce/ui/Container"
34738 ], function(Container) {
34739 	"use strict";
34740 
34741 	return Container.extend({
34742 		Defaults: {
34743 			role: 'toolbar',
34744 			layout: 'flow'
34745 		},
34746 
34747 		/**
34748 		 * Constructs a instance with the specified settings.
34749 		 *
34750 		 * @constructor
34751 		 * @param {Object} settings Name/value object with settings.
34752 		 */
34753 		init: function(settings) {
34754 			var self = this;
34755 
34756 			self._super(settings);
34757 			self.addClass('toolbar');
34758 		},
34759 
34760 		/**
34761 		 * Called after the control has been rendered.
34762 		 *
34763 		 * @method postRender
34764 		 */
34765 		postRender: function() {
34766 			var self = this;
34767 
34768 			self.items().addClass('toolbar-item');
34769 
34770 			return self._super();
34771 		}
34772 	});
34773 });
34774 
34775 // Included from: js/tinymce/classes/ui/MenuBar.js
34776 
34777 /**
34778  * MenuBar.js
34779  *
34780  * Copyright, Moxiecode Systems AB
34781  * Released under LGPL License.
34782  *
34783  * License: http://www.tinymce.com/license
34784  * Contributing: http://www.tinymce.com/contributing
34785  */
34786 
34787 /**
34788  * Creates a new menubar.
34789  *
34790  * @-x-less MenuBar.less
34791  * @class tinymce.ui.MenuBar
34792  * @extends tinymce.ui.Toolbar
34793  */
34794 define("tinymce/ui/MenuBar", [
34795 	"tinymce/ui/Toolbar"
34796 ], function(Toolbar) {
34797 	"use strict";
34798 
34799 	return Toolbar.extend({
34800 		Defaults: {
34801 			role: 'menubar',
34802 			containerCls: 'menubar',
34803 			ariaRoot: true,
34804 			defaults: {
34805 				type: 'menubutton'
34806 			}
34807 		}
34808 	});
34809 });
34810 
34811 // Included from: js/tinymce/classes/ui/Radio.js
34812 
34813 /**
34814  * Radio.js
34815  *
34816  * Copyright, Moxiecode Systems AB
34817  * Released under LGPL License.
34818  *
34819  * License: http://www.tinymce.com/license
34820  * Contributing: http://www.tinymce.com/contributing
34821  */
34822 
34823 /**
34824  * Creates a new radio button.
34825  *
34826  * @-x-less Radio.less
34827  * @class tinymce.ui.Radio
34828  * @extends tinymce.ui.Checkbox
34829  */
34830 define("tinymce/ui/Radio", [
34831 	"tinymce/ui/Checkbox"
34832 ], function(Checkbox) {
34833 	"use strict";
34834 
34835 	return Checkbox.extend({
34836 		Defaults: {
34837 			classes: "radio",
34838 			role: "radio"
34839 		}
34840 	});
34841 });
34842 
34843 // Included from: js/tinymce/classes/ui/ResizeHandle.js
34844 
34845 /**
34846  * ResizeHandle.js
34847  *
34848  * Copyright, Moxiecode Systems AB
34849  * Released under LGPL License.
34850  *
34851  * License: http://www.tinymce.com/license
34852  * Contributing: http://www.tinymce.com/contributing
34853  */
34854 
34855 /**
34856  * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
34857  *
34858  * @-x-less ResizeHandle.less
34859  * @class tinymce.ui.ResizeHandle
34860  * @extends tinymce.ui.Widget
34861  */
34862 define("tinymce/ui/ResizeHandle", [
34863 	"tinymce/ui/Widget",
34864 	"tinymce/ui/DragHelper"
34865 ], function(Widget, DragHelper) {
34866 	"use strict";
34867 
34868 	return Widget.extend({
34869 		/**
34870 		 * Renders the control as a HTML string.
34871 		 *
34872 		 * @method renderHtml
34873 		 * @return {String} HTML representing the control.
34874 		 */
34875 		renderHtml: function() {
34876 			var self = this, prefix = self.classPrefix;
34877 
34878 			self.addClass('resizehandle');
34879 
34880 			if (self.settings.direction == "both") {
34881 				self.addClass('resizehandle-both');
34882 			}
34883 
34884 			self.canFocus = false;
34885 
34886 			return (
34887 				'<div id="' + self._id + '" class="' + self.classes() + '">' +
34888 					'<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
34889 				'</div>'
34890 			);
34891 		},
34892 
34893 		/**
34894 		 * Called after the control has been rendered.
34895 		 *
34896 		 * @method postRender
34897 		 */
34898 		postRender: function() {
34899 			var self = this;
34900 
34901 			self._super();
34902 
34903 			self.resizeDragHelper = new DragHelper(this._id, {
34904 				start: function() {
34905 					self.fire('ResizeStart');
34906 				},
34907 
34908 				drag: function(e) {
34909 					if (self.settings.direction != "both") {
34910 						e.deltaX = 0;
34911 					}
34912 
34913 					self.fire('Resize', e);
34914 				},
34915 
34916 				stop: function() {
34917 					self.fire('ResizeEnd');
34918 				}
34919 			});
34920 		},
34921 
34922 		remove: function() {
34923 			if (this.resizeDragHelper) {
34924 				this.resizeDragHelper.destroy();
34925 			}
34926 
34927 			return this._super();
34928 		}
34929 	});
34930 });
34931 
34932 // Included from: js/tinymce/classes/ui/Spacer.js
34933 
34934 /**
34935  * Spacer.js
34936  *
34937  * Copyright, Moxiecode Systems AB
34938  * Released under LGPL License.
34939  *
34940  * License: http://www.tinymce.com/license
34941  * Contributing: http://www.tinymce.com/contributing
34942  */
34943 
34944 /**
34945  * Creates a spacer. This control is used in flex layouts for example.
34946  *
34947  * @-x-less Spacer.less
34948  * @class tinymce.ui.Spacer
34949  * @extends tinymce.ui.Widget
34950  */
34951 define("tinymce/ui/Spacer", [
34952 	"tinymce/ui/Widget"
34953 ], function(Widget) {
34954 	"use strict";
34955 
34956 	return Widget.extend({
34957 		/**
34958 		 * Renders the control as a HTML string.
34959 		 *
34960 		 * @method renderHtml
34961 		 * @return {String} HTML representing the control.
34962 		 */
34963 		renderHtml: function() {
34964 			var self = this;
34965 
34966 			self.addClass('spacer');
34967 			self.canFocus = false;
34968 
34969 			return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
34970 		}
34971 	});
34972 });
34973 
34974 // Included from: js/tinymce/classes/ui/SplitButton.js
34975 
34976 /**
34977  * SplitButton.js
34978  *
34979  * Copyright, Moxiecode Systems AB
34980  * Released under LGPL License.
34981  *
34982  * License: http://www.tinymce.com/license
34983  * Contributing: http://www.tinymce.com/contributing
34984  */
34985 
34986 /**
34987  * Creates a split button.
34988  *
34989  * @-x-less SplitButton.less
34990  * @class tinymce.ui.SplitButton
34991  * @extends tinymce.ui.MenuButton
34992  */
34993 define("tinymce/ui/SplitButton", [
34994 	"tinymce/ui/MenuButton",
34995 	"tinymce/ui/DomUtils"
34996 ], function(MenuButton, DomUtils) {
34997 	return MenuButton.extend({
34998 		Defaults: {
34999 			classes: "widget btn splitbtn",
35000 			role: "button"
35001 		},
35002 
35003 		/**
35004 		 * Repaints the control after a layout operation.
35005 		 *
35006 		 * @method repaint
35007 		 */
35008 		repaint: function() {
35009 			var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
35010 
35011 			self._super();
35012 
35013 			mainButtonElm = elm.firstChild;
35014 			menuButtonElm = elm.lastChild;
35015 
35016 			DomUtils.css(mainButtonElm, {
35017 				width: rect.w - DomUtils.getSize(menuButtonElm).width,
35018 				height: rect.h - 2
35019 			});
35020 
35021 			DomUtils.css(menuButtonElm, {
35022 				height: rect.h - 2
35023 			});
35024 
35025 			return self;
35026 		},
35027 
35028 		/**
35029 		 * Sets the active menu state.
35030 		 *
35031 		 * @private
35032 		 */
35033 		activeMenu: function(state) {
35034 			var self = this;
35035 
35036 			DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
35037 		},
35038 
35039 		/**
35040 		 * Renders the control as a HTML string.
35041 		 *
35042 		 * @method renderHtml
35043 		 * @return {String} HTML representing the control.
35044 		 */
35045 		renderHtml: function() {
35046 			var self = this, id = self._id, prefix = self.classPrefix, image;
35047 			var icon = self.settings.icon;
35048 
35049 			image = self.settings.image;
35050 			if (image) {
35051 				icon = 'none';
35052 
35053 				// Support for [high dpi, low dpi] image sources
35054 				if (typeof image != "string") {
35055 					image = window.getSelection ? image[0] : image[1];
35056 				}
35057 
35058 				image = ' style="background-image: url(\'' + image + '\')"';
35059 			} else {
35060 				image = '';
35061 			}
35062 
35063 			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
35064 
35065 			return (
35066 				'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
35067 					'<button type="button" hidefocus="1" tabindex="-1">' +
35068 						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
35069 						(self._text ? (icon ? ' ' : '') + self._text : '') +
35070 					'</button>' +
35071 					'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
35072 						//(icon ? '<i class="' + icon + '"></i>' : '') +
35073 						(self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
35074 						' <i class="' + prefix + 'caret"></i>' +
35075 					'</button>' +
35076 				'</div>'
35077 			);
35078 		},
35079 
35080 		/**
35081 		 * Called after the control has been rendered.
35082 		 *
35083 		 * @method postRender
35084 		 */
35085 		postRender: function() {
35086 			var self = this, onClickHandler = self.settings.onclick;
35087 
35088 			self.on('click', function(e) {
35089 				var node = e.target;
35090 
35091 				if (e.control == this) {
35092 					// Find clicks that is on the main button
35093 					while (node) {
35094 						if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
35095 							e.stopImmediatePropagation();
35096 							onClickHandler.call(this, e);
35097 							return;
35098 						}
35099 
35100 						node = node.parentNode;
35101 					}
35102 				}
35103 			});
35104 
35105 			delete self.settings.onclick;
35106 
35107 			return self._super();
35108 		}
35109 	});
35110 });
35111 
35112 // Included from: js/tinymce/classes/ui/StackLayout.js
35113 
35114 /**
35115  * StackLayout.js
35116  *
35117  * Copyright, Moxiecode Systems AB
35118  * Released under LGPL License.
35119  *
35120  * License: http://www.tinymce.com/license
35121  * Contributing: http://www.tinymce.com/contributing
35122  */
35123 
35124 /**
35125  * This layout uses the browsers layout when the items are blocks.
35126  *
35127  * @-x-less StackLayout.less
35128  * @class tinymce.ui.StackLayout
35129  * @extends tinymce.ui.FlowLayout
35130  */
35131 define("tinymce/ui/StackLayout", [
35132 	"tinymce/ui/FlowLayout"
35133 ], function(FlowLayout) {
35134 	"use strict";
35135 
35136 	return FlowLayout.extend({
35137 		Defaults: {
35138 			containerClass: 'stack-layout',
35139 			controlClass: 'stack-layout-item',
35140 			endClass : 'break'
35141 		}
35142 	});
35143 });
35144 
35145 // Included from: js/tinymce/classes/ui/TabPanel.js
35146 
35147 /**
35148  * TabPanel.js
35149  *
35150  * Copyright, Moxiecode Systems AB
35151  * Released under LGPL License.
35152  *
35153  * License: http://www.tinymce.com/license
35154  * Contributing: http://www.tinymce.com/contributing
35155  */
35156 
35157 /**
35158  * Creates a tab panel control.
35159  *
35160  * @-x-less TabPanel.less
35161  * @class tinymce.ui.TabPanel
35162  * @extends tinymce.ui.Panel
35163  *
35164  * @setting {Number} activeTab Active tab index.
35165  */
35166 define("tinymce/ui/TabPanel", [
35167 	"tinymce/ui/Panel",
35168 	"tinymce/ui/DomUtils"
35169 ], function(Panel, DomUtils) {
35170 	"use strict";
35171 
35172 	return Panel.extend({
35173 		Defaults: {
35174 			layout: 'absolute',
35175 			defaults: {
35176 				type: 'panel'
35177 			}
35178 		},
35179 
35180 		/**
35181 		 * Activates the specified tab by index.
35182 		 *
35183 		 * @method activateTab
35184 		 * @param {Number} idx Index of the tab to activate.
35185 		 */
35186 		activateTab: function(idx) {
35187 			var activeTabElm;
35188 
35189 			if (this.activeTabId) {
35190 				activeTabElm = this.getEl(this.activeTabId);
35191 				DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
35192 				activeTabElm.setAttribute('aria-selected', "false");
35193 			}
35194 
35195 			this.activeTabId = 't' + idx;
35196 
35197 			activeTabElm = this.getEl('t' + idx);
35198 			activeTabElm.setAttribute('aria-selected', "true");
35199 			DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
35200 
35201 			this.items()[idx].show().fire('showtab');
35202 			this.reflow();
35203 
35204 			this.items().each(function(item, i) {
35205 				if (idx != i) {
35206 					item.hide();
35207 				}
35208 			});
35209 		},
35210 
35211 		/**
35212 		 * Renders the control as a HTML string.
35213 		 *
35214 		 * @method renderHtml
35215 		 * @return {String} HTML representing the control.
35216 		 */
35217 		renderHtml: function() {
35218 			var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
35219 
35220 			self.preRender();
35221 			layout.preRender(self);
35222 
35223 			self.items().each(function(ctrl, i) {
35224 				var id = self._id + '-t' + i;
35225 
35226 				ctrl.aria('role', 'tabpanel');
35227 				ctrl.aria('labelledby', id);
35228 
35229 				tabsHtml += (
35230 					'<div id="' + id + '" class="' + prefix + 'tab" ' +
35231 						'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
35232 						self.encode(ctrl.settings.title) +
35233 					'</div>'
35234 				);
35235 			});
35236 
35237 			return (
35238 				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
35239 					'<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
35240 						tabsHtml +
35241 					'</div>' +
35242 					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
35243 						layout.renderHtml(self) +
35244 					'</div>' +
35245 				'</div>'
35246 			);
35247 		},
35248 
35249 		/**
35250 		 * Called after the control has been rendered.
35251 		 *
35252 		 * @method postRender
35253 		 */
35254 		postRender: function() {
35255 			var self = this;
35256 
35257 			self._super();
35258 
35259 			self.settings.activeTab = self.settings.activeTab || 0;
35260 			self.activateTab(self.settings.activeTab);
35261 
35262 			this.on('click', function(e) {
35263 				var targetParent = e.target.parentNode;
35264 
35265 				if (e.target.parentNode.id == self._id + '-head') {
35266 					var i = targetParent.childNodes.length;
35267 
35268 					while (i--) {
35269 						if (targetParent.childNodes[i] == e.target) {
35270 							self.activateTab(i);
35271 						}
35272 					}
35273 				}
35274 			});
35275 		},
35276 
35277 		/**
35278 		 * Initializes the current controls layout rect.
35279 		 * This will be executed by the layout managers to determine the
35280 		 * default minWidth/minHeight etc.
35281 		 *
35282 		 * @method initLayoutRect
35283 		 * @return {Object} Layout rect instance.
35284 		 */
35285 		initLayoutRect: function() {
35286 			var self = this, rect, minW, minH;
35287 
35288 			minW = DomUtils.getSize(self.getEl('head')).width;
35289 			minW = minW < 0 ? 0 : minW;
35290 			minH = 0;
35291 
35292 			self.items().each(function(item) {
35293 				minW = Math.max(minW, item.layoutRect().minW);
35294 				minH = Math.max(minH, item.layoutRect().minH);
35295 			});
35296 
35297 			self.items().each(function(ctrl) {
35298 				ctrl.settings.x = 0;
35299 				ctrl.settings.y = 0;
35300 				ctrl.settings.w = minW;
35301 				ctrl.settings.h = minH;
35302 
35303 				ctrl.layoutRect({
35304 					x: 0,
35305 					y: 0,
35306 					w: minW,
35307 					h: minH
35308 				});
35309 			});
35310 
35311 			var headH = DomUtils.getSize(self.getEl('head')).height;
35312 
35313 			self.settings.minWidth = minW;
35314 			self.settings.minHeight = minH + headH;
35315 
35316 			rect = self._super();
35317 			rect.deltaH += headH;
35318 			rect.innerH = rect.h - rect.deltaH;
35319 
35320 			return rect;
35321 		}
35322 	});
35323 });
35324 
35325 // Included from: js/tinymce/classes/ui/TextBox.js
35326 
35327 /**
35328  * TextBox.js
35329  *
35330  * Copyright, Moxiecode Systems AB
35331  * Released under LGPL License.
35332  *
35333  * License: http://www.tinymce.com/license
35334  * Contributing: http://www.tinymce.com/contributing
35335  */
35336 
35337 /**
35338  * Creates a new textbox.
35339  *
35340  * @-x-less TextBox.less
35341  * @class tinymce.ui.TextBox
35342  * @extends tinymce.ui.Widget
35343  */
35344 define("tinymce/ui/TextBox", [
35345 	"tinymce/ui/Widget",
35346 	"tinymce/ui/DomUtils"
35347 ], function(Widget, DomUtils) {
35348 	"use strict";
35349 
35350 	return Widget.extend({
35351 		/**
35352 		 * Constructs a instance with the specified settings.
35353 		 *
35354 		 * @constructor
35355 		 * @param {Object} settings Name/value object with settings.
35356 		 * @setting {Boolean} multiline True if the textbox is a multiline control.
35357 		 * @setting {Number} maxLength Max length for the textbox.
35358 		 * @setting {Number} size Size of the textbox in characters.
35359 		 */
35360 		init: function(settings) {
35361 			var self = this;
35362 
35363 			self._super(settings);
35364 
35365 			self._value = settings.value || '';
35366 			self.addClass('textbox');
35367 
35368 			if (settings.multiline) {
35369 				self.addClass('multiline');
35370 			} else {
35371 				// TODO: Rework this
35372 				self.on('keydown', function(e) {
35373 					if (e.keyCode == 13) {
35374 						self.parents().reverse().each(function(ctrl) {
35375 							e.preventDefault();
35376 
35377 							if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
35378 								ctrl.fire('submit', {data: ctrl.toJSON()});
35379 								return false;
35380 							}
35381 						});
35382 					}
35383 				});
35384 			}
35385 		},
35386 
35387 		/**
35388 		 * Getter/setter function for the disabled state.
35389 		 *
35390 		 * @method value
35391 		 * @param {Boolean} [state] State to be set.
35392 		 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
35393 		 */
35394 		disabled: function(state) {
35395 			var self = this;
35396 
35397 			if (self._rendered && typeof(state) != 'undefined') {
35398 				self.getEl().disabled = state;
35399 			}
35400 
35401 			return self._super(state);
35402 		},
35403 
35404 		/**
35405 		 * Getter/setter function for the control value.
35406 		 *
35407 		 * @method value
35408 		 * @param {String} [value] Value to be set.
35409 		 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
35410 		 */
35411 		value: function(value) {
35412 			var self = this;
35413 
35414 			if (typeof(value) != "undefined") {
35415 				self._value = value;
35416 
35417 				if (self._rendered) {
35418 					self.getEl().value = value;
35419 				}
35420 
35421 				return self;
35422 			}
35423 
35424 			if (self._rendered) {
35425 				return self.getEl().value;
35426 			}
35427 
35428 			return self._value;
35429 		},
35430 
35431 		/**
35432 		 * Repaints the control after a layout operation.
35433 		 *
35434 		 * @method repaint
35435 		 */
35436 		repaint: function() {
35437 			var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
35438 
35439 			style = self.getEl().style;
35440 			rect = self._layoutRect;
35441 			lastRepaintRect = self._lastRepaintRect || {};
35442 
35443 			// Detect old IE 7+8 add lineHeight to align caret vertically in the middle
35444 			var doc = document;
35445 			if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
35446 				style.lineHeight = (rect.h - borderH) + 'px';
35447 			}
35448 
35449 			borderBox = self._borderBox;
35450 			borderW = borderBox.left + borderBox.right + 8;
35451 			borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
35452 
35453 			if (rect.x !== lastRepaintRect.x) {
35454 				style.left = rect.x + 'px';
35455 				lastRepaintRect.x = rect.x;
35456 			}
35457 
35458 			if (rect.y !== lastRepaintRect.y) {
35459 				style.top = rect.y + 'px';
35460 				lastRepaintRect.y = rect.y;
35461 			}
35462 
35463 			if (rect.w !== lastRepaintRect.w) {
35464 				style.width = (rect.w - borderW) + 'px';
35465 				lastRepaintRect.w = rect.w;
35466 			}
35467 
35468 			if (rect.h !== lastRepaintRect.h) {
35469 				style.height = (rect.h - borderH) + 'px';
35470 				lastRepaintRect.h = rect.h;
35471 			}
35472 
35473 			self._lastRepaintRect = lastRepaintRect;
35474 			self.fire('repaint', {}, false);
35475 
35476 			return self;
35477 		},
35478 
35479 		/**
35480 		 * Renders the control as a HTML string.
35481 		 *
35482 		 * @method renderHtml
35483 		 * @return {String} HTML representing the control.
35484 		 */
35485 		renderHtml: function() {
35486 			var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';
35487 
35488 			if ("spellcheck" in settings) {
35489 				extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
35490 			}
35491 
35492 			if (settings.maxLength) {
35493 				extraAttrs += ' maxlength="' + settings.maxLength + '"';
35494 			}
35495 
35496 			if (settings.size) {
35497 				extraAttrs += ' size="' + settings.size + '"';
35498 			}
35499 
35500 			if (settings.subtype) {
35501 				extraAttrs += ' type="' + settings.subtype + '"';
35502 			}
35503 
35504 			if (self.disabled()) {
35505 				extraAttrs += ' disabled="disabled"';
35506 			}
35507 
35508 			if (settings.multiline) {
35509 				return (
35510 					'<textarea id="' + id + '" class="' + self.classes() + '" ' +
35511 					(settings.rows ? ' rows="' + settings.rows + '"' : '') +
35512 					' hidefocus="1"' + extraAttrs + '>' + value +
35513 					'</textarea>'
35514 				);
35515 			}
35516 
35517 			return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
35518 		},
35519 
35520 		/**
35521 		 * Called after the control has been rendered.
35522 		 *
35523 		 * @method postRender
35524 		 */
35525 		postRender: function() {
35526 			var self = this;
35527 
35528 			DomUtils.on(self.getEl(), 'change', function(e) {
35529 				self.fire('change', e);
35530 			});
35531 
35532 			return self._super();
35533 		},
35534 
35535 		remove: function() {
35536 			DomUtils.off(this.getEl());
35537 			this._super();
35538 		}
35539 	});
35540 });
35541 
35542 // Included from: js/tinymce/classes/ui/Throbber.js
35543 
35544 /**
35545  * Throbber.js
35546  *
35547  * Copyright, Moxiecode Systems AB
35548  * Released under LGPL License.
35549  *
35550  * License: http://www.tinymce.com/license
35551  * Contributing: http://www.tinymce.com/contributing
35552  */
35553 
35554 /**
35555  * This class enables you to display a Throbber for any element.
35556  *
35557  * @-x-less Throbber.less
35558  * @class tinymce.ui.Throbber
35559  */
35560 define("tinymce/ui/Throbber", [
35561 	"tinymce/ui/DomUtils",
35562 	"tinymce/ui/Control"
35563 ], function(DomUtils, Control) {
35564 	"use strict";
35565 
35566 	/**
35567 	 * Constructs a new throbber.
35568 	 *
35569 	 * @constructor
35570 	 * @param {Element} elm DOM Html element to display throbber in.
35571 	 * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
35572 	 */
35573 	return function(elm, inline) {
35574 		var self = this, state, classPrefix = Control.classPrefix;
35575 
35576 		/**
35577 		 * Shows the throbber.
35578 		 *
35579 		 * @method show
35580 		 * @param {Number} [time] Time to wait before showing.
35581 		 * @return {tinymce.ui.Throbber} Current throbber instance.
35582 		 */
35583 		self.show = function(time) {
35584 			self.hide();
35585 
35586 			state = true;
35587 
35588 			window.setTimeout(function() {
35589 				if (state) {
35590 					elm.appendChild(DomUtils.createFragment(
35591 						'<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
35592 					));
35593 				}
35594 			}, time || 0);
35595 
35596 			return self;
35597 		};
35598 
35599 		/**
35600 		 * Hides the throbber.
35601 		 *
35602 		 * @method hide
35603 		 * @return {tinymce.ui.Throbber} Current throbber instance.
35604 		 */
35605 		self.hide = function() {
35606 			var child = elm.lastChild;
35607 
35608 			if (child && child.className.indexOf('throbber') != -1) {
35609 				child.parentNode.removeChild(child);
35610 			}
35611 
35612 			state = false;
35613 
35614 			return self;
35615 		};
35616 	};
35617 });
35618 
35619 expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/util/Tools","tinymce/Env","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/dom/Range","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/dom/ElementUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
35620 })(this);